# MATTR VII Platform API Overview URL: /docs/api-reference Description: The MATTR VII Platform API defines a set of capabilities that can be used to manage and interact with a MATTR VII tenant. The MATTR VII API defines a set of capabilities that can be used to manage and interact with a MATTR VII tenant. This includes managing a Verifiable Credential across its lifecycle (issue-hold-verify) as well as various tenant administration and management tasks such as setting up a custom domain, creating identifiers and configuring issuance and verification workflows. ## OpenAPI Specifications [#openapi-specifications] Download the complete OpenAPI specifications for the MATTR VII Platform API:
## Getting Started [#getting-started] ### Choose an endpoint [#choose-an-endpoint] Select an endpoint to make a request to. The following resources might be helpful: * The API Reference offers an exhaustive list of all available endpoints and their request structures in different languages. * Different tutorials and guides can be used to learn what endpoints are required for specific capabilities and workflows. * Refer to the [Access control](/docs/platform-management/access-control) section to learn more about what endpoints your client will be able to access. We recommend using the MATTR VII [Postman collection](#postman-collection) to make requests to your MATTR VII tenant. While this isn't an explicit requirement it can really speed things up. ### Obtain an access token [#obtain-an-access-token] Most of the MATTR VII endpoints are protected and require providing a bearer access token when making a request. If you are making a request to an unprotected endpoint (as detailed in the API Reference), you do not need to obtain an access token and can continue to the next step. Use your [access credentials](/docs/platform-management/portal#getting-started) and make a request of the following structure to obtain an access token: ```http title="Request" POST https://{auth_server}/oauth/token ``` * `auth_server` : Replace with the `auth_url` value obtained when you created your tenant. ```json title="Request Body" { "client_id": "F5qae****************************", "client_secret": "Wzc8J**********************************************************", "audience": "learn.vii.au01.mattr.global", "grant_type": "client_credentials" } ``` * `client_id` : Replace with the `client_id` value obtained when you created your tenant. * `client_secret` : Replace with the `client_secret` value obtained when you created your tenant. * `audience` : Replace with the `audience` value obtained when you created your tenant. * `grant_type` : Always use `client_credentials` as a static value, regardless of your specific [login credentials](/docs/platform-management/portal#getting-started). *Response* ```json title="Response body" { "access_token": "eyJhb********************************************************************", // [!code focus] "expires_in": 14400, "token_type": "Bearer" } ``` * The returned `access_token` will enable access to endpoints as per the role assigned to the client. Refer to [Access control](/docs/platform-management/access-control) for more Information. * You will need to obtain a new access token whenever it expires. Our [Postman collection](#postman-collection) includes a pre-request script that obtains an access token when it is missing or has expired. ### Construct the request [#construct-the-request] Construct an API request using the selected endpoint path and the `tenant_url` value obtained when you created your tenant: ```http title="Request template" {method} https://{tenant_url}/{path} ``` For example, a request to [retrieve all IACAs](/docs/api-reference/platform/iaca/list-mobile-credential-iaca) from a tenant whose `tenant_url` is `learn.vii.au01.mattr.global` should be constructed as follows: ```http title="Request example" GET https://learn.vii.au01.mattr.global/v2/credentials/mobile/iacas ``` If the operation has a request body you should structure it too, based on the details provided in the [API Reference](/docs/api-reference) or relevant tutorial. Whatever tool or language your are using to make the request, make sure you include the `access_token` in the request header when making requests to protected endpoints. Refer to the [API Reference](/docs/api-reference) for request samples. ### Handle the response [#handle-the-response] The endpoint would respond with a standard [HTTP status code](https://www.rfc-editor.org/rfc/rfc9110.html#name-status-codes) and a response body. These differ between endpoints and are detailed in the API Reference. You can now adjust your implementation to handle these responses to achieve the desired outcome. ## Concepts [#concepts] ### Pagination [#pagination] Most list operations in the API enable cursor pagination using the `cursor` and `limit` query parameters: **Example on [Retrieve List of Credentials](#operation/retrieveListCreds)** ``` GET https://{tenant-url}/v2/credentials ?limit=100 &cursor=Y3JlYXRlZEF0PTIwMjAtMTAtMDhUMjMlM0ExMyUzQTE3Ljg5NtZGUxZWEyNzQ4MWI4 ``` * `limit`: determines how many entries are returned in that request, with a maximum value of 1000. * `cursor`: sets the location in the retrieved list to get the next batch of entries from. This is based on the returned `nextCursor`, found at the beginning of each returned range and identifies the last object in the list. Requesting an entry after the last list value will return an empty `data` object: ```json { "data": [] } ``` Not providing a query parameter defaults the response to return the first range of entries with a `limit` of 100. ### Authorization [#authorization] Access to the API is granted by our authorization provider. Use the `auth_url`, `audience`, `client_id` and `client_secret` provided with your tenant details to [make a request](/docs/api-reference/platform/security/authToken) to receive a bearer token from the auth provider. This token must then be used as an `authorization` header for all requests to protected endpoints (this is required for the majority of operations). The returned bearer token will only enable access to endpoints as per your client's defined role. Refer to [Access Control](#access-control) for more information. ### Access control [#access-control] MATTR VII uses **Role-Based Access Control (RBAC)** to manage permissions and access within a tenant. Each role grants access to specific capabilities, ensuring that users or clients only have access to the functionalities they need. Below is a list of available roles and their descriptions: * Tenant admin: Has full access to all tenant capabilities. This role is assigned to the default client when a new tenant is created. * Issuer: Has access to capabilities required for issuing and managing credentials of different formats across different channels. * Verifier: Has access to capabilities required for verifying credentials of different formats across different channels. * DTS provider: Has access to capabilities required for managing a Digital trust service (DTS). * DTS consumer: Has access to capabilities required to consume DTS information from a tenant. * Auditor: Has read-only access to analytics data. Each restricted endpoint includes a `Roles` property that indicates what roles are required to access it. ### Rate limiting [#rate-limiting] To ensure platform stability and consistent resource allocation, API requests are subject to rate limiting. This helps protect the service from excessive usage patterns and ensures consistent performance for all users. Every response from the MATTR VII Platform API includes the following rate limit headers: * `x-ratelimit-limit`: Maximum requests allowed in time window. * `x-ratelimit-remaining`: Requests remaining in current window. * `x-ratelimit-reset`: Timestamp when the window resets. If you exceed the rate limit, the API will return a `429 Too Many Requests` response, and you should pause further requests until the rate limit resets: ```http title="Rate limit exceeded response" HTTP/1.1 429 Too Many Requests Content-Type: application/json x-ratelimit-limit: 10 x-ratelimit-remaining: 0 x-ratelimit-reset: 1768791002 { "message": "Too many requests, please try again later." } ``` If you're consistently hitting rate limits or believe your use case requires adjusted limits, please [contact our support team](mailto:dev-support@mattr.global) to discuss your requirements. ## Previous versions [#previous-versions] This is the latest version of the MATTR VII Platform API. Refer to the [MATTR VII Changelog](/docs/resources/changelog/vii#supported-versions) for links to previous versions. ## Postman collection [#postman-collection] The MATTR VII Platform API postman collection provides a simple interface to use the MATTR VII Platform API. Its categorized endpoints, sample request body and pre-request scripts make it easier and quicker to interact with your tenant. Follow the steps below to get started with the Postman collection: **Step 1: Install Postman** Visit the Postman [downloads page](https://www.postman.com/downloads/) to install a desktop client for your system. **Step 2: Download collection and environment files** Download the following files to your local machine: * [`Platform API Collection`](/files/mattr_vii_platform_postman_collection.json): This Postman Collection includes API operations and some configuration. * [`Tenant Environment`](/files/mattr_vii_platform_postman_environment.json): This file defines variables for your MATTR VII tenant. You will need to update some of these variables with your tenant details in a later step. **Step 3: Import collection and environment files into Postman** 1. Open Postman. 2. Select the **Import** button in the *My Workspace* area. 3. Select the local versions of the *Tenant Environment* and the *Platform API Collection* files you downloaded in the previous step. **Step 4: Update environment variables** 1. Select the **Environments** button in the *My Workspace* sidebar. 2. Hover over the *MATTR VII Tenant* name in the environments list and select **Set Active** (you should see it marked as active). 3. Select the *MATTR VII Tenant* environment. 4. Update the following variables with your tenant details: * `baseUrl`: Replace with your tenant's URL. * `auth0Base`: Replace with your Authentication server URL. * `tenantClientId`: Replace with your Client ID. * `tenantClientSecret`: Replace with your Client Secret. * `tenantAudience`: Replace with your tenant's URL. If you are unsure of any of these details, please [contact us](mailto:dev-support@mattr.global). **Step 5: Try it out** 1. Select the **Collections** button in the *My Workspace* sidebar. 2. Select the MATTR VII API collection. 3. Select the *Retrieve all IACAs* endpoint (You can find it under *Tenant configuration > Identifiers > IACA*). 4. Select **Send** in the top right corner of the request pane. 5. The response should be displayed in the *Response* pane (If this is a new tenant, the response is likely to be empty). # MATTR VII Management API Overview URL: /docs/api-reference/management-api-reference-overview Description: The MATTR VII Management API enables administrators to perform actions that span across multiple tenants. The MATTR VII Management API enables administrators to perform actions that span across multiple tenants. It provides capabilities for creating, updating, and managing tenants, as well as configuring access controls, defining who can access each tenant, with what roles, and which permissions. This API is essential for orchestrating large-scale deployments and maintaining centralized oversight of your digital trust infrastructure. ## OpenAPI Specifications [#openapi-specifications] Download the complete OpenAPI specifications for the MATTR VII Management API:
## Getting Started [#getting-started] ### Obtain an access token [#obtain-an-access-token] Before you can make any API requests to manage your MATTR VII tenants, you must complete authentication by obtaining a management API access token from our authentication provider. Use your management API client credentials to make a request of the following structure: ```http title="Request" POST https://auth.manage.au01.mattr.global/oauth/token ``` ```json title="Request body" { "client_id": "F5qae****************************", "client_secret": "Wzc8J**********************************************************", "audience": "https://manage.au01.mattr.global", "grant_type": "client_credentials" } ``` * `client_id` : Replace with the `client_id` value from your management API client credentials. * `client_secret` : Replace with the `client_secret` value from your management API client credentials. * `audience` : Always use `https://manage.au01.mattr.global` as a static value, regardless of your specific management API client credentials. * `grant_type` : Always use `client_credentials` as a static value, regardless of your specific management API client credentials. These are not the same `client_id` and `client_secret` you were provided for accessing other MATTR VII APIs, but rather unique credentials for accessing the Management APIs. If you have not received these credentials or have any questions, please [contact us](mailto:dev-support@mattr.global) before proceeding. *Response* ```json title="Response body" { "access_token": "eyJhb********************************************************************", // [!code focus] "expires_in": 14400, "token_type": "Bearer" } ``` The returned `access_token` must be used as a bearer token for all subsequent requests to any of the protected MATTR VII Management API endpoints. ### Construct the request [#construct-the-request] Construct an API request using the selected endpoint path and the specific region value for your tenant. Use the following template to structure the request: ```http title="Request template" {method} https://manage.{region}.mattr.global/{path} ``` For example, a request to [retrieve all tenants](/docs/api-reference/management/tenants/getTenants) in the `au01` region should be constructed as follows: ```http title="Request example" GET https://manage.au01.mattr.global/v1/tenants ``` If the operation has a request body you should structure it too, based on the details provided in the API Reference or relevant tutorials. Whatever tool or language you are using to make the request, make sure you include the `access_token` in the request header when making requests to protected endpoints. Refer to the API Reference for request samples. ### Handle the response [#handle-the-response] The endpoint would respond with a standard [HTTP status code](https://www.rfc-editor.org/rfc/rfc9110.html#name-status-codes) and a response body. These differ between endpoints and are detailed in the API Reference. You can now adjust your implementation to handle these responses to achieve the desired outcome. ## Concepts [#concepts] ### Pagination [#pagination] Most list operations in the API enable cursor pagination using the `cursor` and `limit` query parameters: **Example on [retrieve all tenants](/docs/api-reference/management/tenants/getTenants)** ``` GET https://manage.au01.mattr.global/v1/tenants ?limit=100 &cursor=Y3JlYXRlZEF0PTIwMjAtMTAtMDhUMjMlM0ExMyUzQTE3Ljg5NtZGUxZWEyNzQ4MWI4 ``` * `limit`: determines how many entries are returned in that request, with a maximum value of 1000. * `cursor`: sets the location in the retrieved list to get the next batch of entries from. This is based on the returned `nextCursor`, found at the beginning of each returned range and identifies the last object in the list. Requesting an entry after the last list value will return an empty `data` object: ```json { "data": [] } ``` Not providing a query parameter defaults the response to return the first range of entries with a `limit` of 100. ### Authorization [#authorization] The Management API is a separate set of APIs to MATTR VII. It uses machine-to-machine authentication through its own credentials, which are different from your MATTR VII client credentials. As part of onboarding you will be provided with the required details to make a call to a dedicated management API authorization provider and receive a bearer token. This token is then used in an `authorization` header on all calls identified as requiring `bearerAuth` (this is required for the majority of management operations). ### Rate limiting [#rate-limiting] To ensure platform stability and consistent resource allocation, API requests are subject to rate limiting. This helps protect the service from excessive usage patterns and ensures consistent performance for all users. Every response from the MATTR VII Management API includes the following rate limit headers: * `x-ratelimit-limit`: Maximum requests allowed in time window. * `x-ratelimit-remaining`: Requests remaining in current window. * `x-ratelimit-reset`: Timestamp when the window resets. If you exceed the rate limit, the API will return a `429 Too Many Requests` response, and you should pause further requests until the rate limit resets: ```http title="Rate limit exceeded response" HTTP/1.1 429 Too Many Requests Content-Type: application/json x-ratelimit-limit: 10 x-ratelimit-remaining: 0 x-ratelimit-reset: 1768791002 { "message": "Too many requests, please try again later." } ``` If you're consistently hitting rate limits or believe your use case requires adjusted limits, please [contact our support team](mailto:dev-support@mattr.global) to discuss your requirements. ## Previous versions [#previous-versions] This is the latest version of the MATTR VII Management API. Refer to the [MATTR VII Changelog](/docs/resources/changelog/vii#supported-versions) for links to previous versions. # How does a DTS chain of trust work and what are the requirements for DTS certificates? URL: /docs/digital-trust-service/certificates-overview Description: How the Digital Trust Service chain of trust works, the 2-tier and 3-tier certificate models, and the requirements for DTS root CA, intermediate CA, and signer certificates used to sign VICALs and RICALs. ## Chain of trust [#chain-of-trust] When DTS operators publish trusted lists (such as a trusted list of mDoc issuers in the form of a [VICAL](/docs/digital-trust-service/vical-overview), or a trusted list of verifiers in the form of a [RICAL](/docs/digital-trust-service/rical-overview)) they must ensure that consumers can verify the authenticity and integrity of these lists. This is accomplished using a chain of trust, which is established through a hierarchy of certificates. Each trusted list is signed by a **signer certificate** that chains back to a DTS root CA: a VICAL Signer Certificate (VSC) for a VICAL, or a RICAL Signer Certificate (RSC) for a RICAL. The same DTS certificate models and requirements described on this page apply to both. The following diagram depicts how MATTR implements the chain of trust model when signing trusted lists: Chain of trust ## Certificate models: 2-tier and 3-tier [#certificate-models-2-tier-and-3-tier] MATTR VII supports two certificate models for signing trusted lists, depending on whether an intermediate CA certificate is included in the chain of trust: * **2-tier model**: The chain consists of a DTS root CA certificate that directly signs the signer certificate (VSC or RSC). This is the default model. * **3-tier model**: The chain includes an additional DTS intermediate CA certificate between the DTS root CA and the signer certificate (VSC or RSC). The DTS root CA signs the intermediate CA, and the intermediate CA signs the signer certificate. The certificate model is determined by the `useIntermediateCa` property on the DTS root CA certificate. When set to `true`, the DTS root CA requires and uses intermediate CA certificates as part of its chain of trust for signing operations. The 3-tier model is only supported for [unmanaged (external)](/docs/concepts/chain-of-trust#external-certificates) DTS certificates. You cannot enable intermediate CA certificates for managed DTS root CA certificates, and intermediate CA certificates themselves can only be unmanaged (external). In other words, both the DTS root CA and the DTS intermediate CA must be unmanaged to use the 3-tier model. ### Choosing a model [#choosing-a-model] Consider the following when deciding between the two models: * **Operational simplicity**: The 2-tier model involves fewer certificates to issue, manage, and renew. If you do not have a specific requirement for an intermediate CA, the 2-tier model is simpler to operate. * **Key protection and isolation**: The 3-tier model lets you keep the DTS root CA private key offline and use the intermediate CA for day-to-day signing. This reduces exposure of the root key and limits the impact if an intermediate CA is compromised, as you can revoke and replace the intermediate without rotating the root. * **PKI alignment**: Choose the 3-tier model if your organization's existing Public Key Infrastructure (PKI) policies require a layered hierarchy with intermediate CAs, or if you need to delegate signing to separate intermediate authorities. * **Management method**: The 3-tier model requires unmanaged (external) certificates for both the root and intermediate CA. If you want MATTR VII to manage your DTS root CA, you must use the 2-tier model. ## Certificate requirements [#certificate-requirements] The following list describes the requirements for DTS certificates used in MATTR VII. Some of the requirements are common across all certificates, while others are specific to the type of certificate (DTS root CA, signer certificate). The signer certificate requirements apply to both the VICAL Signer Certificate (VSC) and the RICAL Signer Certificate (RSC). * When using managed DTS certificates, MATTR VII **automatically** ensures that all certificates meet these requirements. * When using [unmanaged (external)](/docs/concepts/chain-of-trust#external-certificates) DTS certificates, it is the responsibility of the **DTS provider** to ensure compliance. ### Common certificate requirements [#common-certificate-requirements] * Certificate format & basic attributes: * PEM format must contain a valid X.509 certificate. * Version must be v3. * `Issuer` field must be present and valid. * Issuer Alternative Name must be present and contain a valid email address or URI. * Serial Number: * Must be present. * Must contain 1-20 digits (**Best practice**: Use a positive, non-sequential value). * Subject attributes: * Subject field must be present: * Country (C): must be present and be a valid [ISO 3166-1 alpha-2 code](https://www.iso.org/glossary-for-iso-3166.html). * Common Name (CN): must be present and non-empty. * Organization (O): must be present and non-empty. * Public Key requirements: * Subject Public Key must be present. * Extensions: * Certificate must include extensions. * Duplicate extensions are not allowed (No more than one extension with the same `extnID`). * Mandatory extensions: * Subject Key Identifier: Must be present and non-empty. * Key Usage: * Must be present. * Must match the intended use of the certificate (e.g. DTS root CA, VSC, RSC). * Validity period: * `NotAfter` must be after `NotBefore`. * `NotAfter` cannot be in the past (expired certificates are invalid). * CA certificates (DTS root CA and DTS intermediate CA) can be future dated (`notBefore` can be in the future). * Signer certificates (VSC or RSC) cannot be future dated (`notBefore` cannot be in the future). * Must be within allowed limits for certificate type: * Signer certificate (VSC or RSC): Maximum 1187 days from issuance. * Certificate Revocation List (CRL): * If a CRL is provided, it must be valid and signed by the DTS root CA. * The CRL must be accessible via a valid URI. ### DTS root CA specific requirements [#dts-root-ca-specific-requirements] * Must include the `keyCertSign` and `cRLSign` key usages. * Basic constraints must be present and `CA` must be set to `TRUE`. * Issuer Alternative Name must be present and contain a valid email address or URI. * Signature must be self-signed and verifiable. * Public key must use one of the supported public key algorithms and curves as defined in ISO/IEC 18013-5:2021 B.3: * ECDSA curves: `P-256`, `P-384`, `P-521`, `brainpoolP256r1`, `brainpoolP320r1`, `brainpoolP384r1`, `brainpoolP512r1` * EdDSA key types: `Ed25519`, `Ed448` ### DTS intermediate CA specific requirements [#dts-intermediate-ca-specific-requirements] These requirements apply only when using the [3-tier model](#certificate-models-2-tier-and-3-tier). * Must be signed by a valid DTS root CA that has `useIntermediateCa` set to `true`. * Must include the `keyCertSign` key usage. * Basic constraints must be present and `CA` must be set to `TRUE`. * `Issuer` field must be present and must match the exact binary value of the DTS root CA certificate subject. * Authority Key Identifier must be present and match the DTS root CA's Subject Key Identifier. * The country must match the country of the DTS root CA certificate. * Must not exceed the parent DTS root CA's validity period (i.e. `notBefore` and `notAfter` must be within the DTS root CA's validity period). * Signature must be verifiable against the DTS root CA. ### Signer certificate specific requirements (VSC and RSC) [#signer-certificate-specific-requirements-vsc-and-rsc] These requirements apply to both the VICAL Signer Certificate (VSC) and the RICAL Signer Certificate (RSC). * Must be signed by a valid signing CA: the DTS root CA in the 2-tier model, or the DTS intermediate CA in the [3-tier model](#certificate-models-2-tier-and-3-tier). * Common name must differ from the parent signing CA. * `Issuer` field must be present and must match the exact binary value of the parent signing CA certificate subject. * Must include the `nonRepudiation` key usage exclusively. * Extended key usage must be present and include the correct OID: * `1.0.18013.5.1.8`. * Signature must be verifiable against the parent signing CA. * Authority Key Identifier must be present and match the parent signing CA's Subject Key Identifier. * Must not exceed the parent signing CA's validity period (i.e. `notBefore` and `notAfter` must be within the parent signing CA's validity period). * Public key must match the CSR provided during signer creation. ### Example DTS certificates [#example-dts-certificates] See an example of valid certificates parsed using the MATTR Labs [X.509 certificate decoder](https://tools.mattrlabs.com/pem): * [DTS root CA](https://tools.mattrlabs.com/pem?cert=MIICaTCCAhCgAwIBAgIKZxRbYxAhB8suGjAKBggqhkjOPQQDAjBVMQswCQYDVQQGEwJBVTEvMC0GA1UEAwwmdHV0b3JpYWwudmlpLmF1NzAxLm1hdHRybGFicy5pbyBEVFMgQ0ExFTATBgNVBAoMDEV4YW1wbGUgaW5jLjAeFw0yNTA4MjcyMjU2NDVaFw00NTA4MjgwMDAwMDBaMFUxCzAJBgNVBAYTAkFVMS8wLQYDVQQDDCZ0dXRvcmlhbC52aWkuYXU3MDEubWF0dHJsYWJzLmlvIERUUyBDQTEVMBMGA1UECgwMRXhhbXBsZSBpbmMuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAKelCNyLAqcqNPvCxYjZIq8f4BRGC6rVmHRBhROS3pWQN5atU9OdYfpd7ZHPKEoj568bczSlnmXih9pLtTAaBqOBxzCBxDAdBgNVHQ4EFgQUPWkAFCKyDKb%252BtEoAz1gNtHZSQ7AwDgYDVR0PAQH%252FBAQDAgEGMBIGA1UdEwEB%252FwQIMAYBAf8CAQAwfwYDVR0fBHgwdjB0oHKgcIZuaHR0cHM6Ly90dXRvcmlhbC52aWkuYXU3MDEubWF0dHJsYWJzLmlvL3YxL2Vjb3N5c3RlbXMvY2VydGlmaWNhdGVzL2NhLzViZjViMGEzLWRlMTYtNGY2Ni05ZWE5LTEzNmI4YTgyMmQ4NS9jcmwwCgYIKoZIzj0EAwIDRwAwRAIgSnaPUlBoxzuhrdz2%252Fnroi0RvQI2C97ZAX%252FF%252Bx%252FLlY2QCIBnrqXjr3VdySNSwD1lfx1e66deKtux82S%252BfTdewCg%252Fl) * [VSC](https://tools.mattrlabs.com/pem?cert=MIIClDCCAjqgAwIBAgIKRcoBxY4OKBrQpTAKBggqhkjOPQQDAjBVMQswCQYDVQQGEwJBVTEvMC0GA1UEAwwmdHV0b3JpYWwudmlpLmF1NzAxLm1hdHRybGFicy5pbyBEVFMgQ0ExFTATBgNVBAoMDEV4YW1wbGUgaW5jLjAeFw0yNTA4MjcyMjU2NDVaFw0yODExMjYyMjU2NDVaMFsxCzAJBgNVBAYTAkFVMTUwMwYDVQQDDCx0dXRvcmlhbC52aWkuYXU3MDEubWF0dHJsYWJzLmlvIFZJQ0FMIFNpZ25lcjEVMBMGA1UECgwMRXhhbXBsZSBpbmMuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfyfYFgoFXvm4ra2cpsYC9E%252FztcgKNwxBaxbY%252BH3dCliIbWaP3Shf%252BWgGjObSw24C8QT34d8ZRvrM6jCBBgKdXKOB6zCB6DAdBgNVHQ4EFgQUF6LkXvQrrAqYtkDBjkbk9po7KHQwDgYDVR0PAQH%252FBAQDAgZAMH8GA1UdHwR4MHYwdKByoHCGbmh0dHBzOi8vdHV0b3JpYWwudmlpLmF1NzAxLm1hdHRybGFicy5pby92MS9lY29zeXN0ZW1zL2NlcnRpZmljYXRlcy9jYS81YmY1YjBhMy1kZTE2LTRmNjYtOWVhOS0xMzZiOGE4MjJkODUvY3JsMB8GA1UdIwQYMBaAFD1pABQisgym%252FrRKAM9YDbR2UkOwMBUGA1UdJQEB%252FwQLMAkGByiBjF0FAQgwCgYIKoZIzj0EAwIDSAAwRQIgPJCn7jsVyCOIolwYE7HqBll4Xvt6BHm2Iw%252BMPH78m0MCIQCC3NB8mx8G3cSNudJ7H0lQqg9QZP2r4sAX%252FPEIqxqFdA%253D%253D) # DTS options URL: /docs/digital-trust-service/consuming-trust-overview Description: The options MATTR offers for publishing and consuming trust, and which category of trusted list each one serves. MATTR offers several options for publishing and consuming trust. Each option serves one of the categories described in [Trusted Lists](/docs/digital-trust-service/trusted-lists), so you can pick the mechanism that matches the kind of trust you need to publish or consume. There are different models for a Digital Trust Service to make its trust framework available for consumption. You can download trust information from publicly available websites, from public APIs, or from authenticated APIs (which may require onboarding and potentially involve commercial terms). ## Ecosystem policies via MATTR VII APIs [#ecosystem-policies-via-mattr-vii-apis] **Serves:** Trusted Issuer Lists and Trusted Reader Lists. [MATTR VII ecosystem capabilities](/docs/digital-trust-service) let a network operator publish policies that describe which participants may issue and which may verify each credential type. Issuers, holders, and verifiers retrieve this information through publicly available or authenticated APIs and integrate it into their solutions. A policy defines what credential types (identified by their **format** and **type/docType**) can be **issued** and/or **verified** by which participants (identified by their **IACA/DID**). Retrieve the current policy through the [ecosystem policy API](/docs/api-reference/platform/policy/getLatestEcosystemPolicy), or consume it through the MATTR Pi [Holder](/docs/holding/sdk-overview), [Verifier Web](/docs/verification/remote-web-verifiers/sdks/overview), and [Verifier Mobile](/docs/verification/remote-mobile-verifiers/sdks/overview) SDKs, or through MATTR [GO Hold](/docs/holding/go-hold/getting-started) and [GO Verify](/docs/verification/go-verify/getting-started). ## VICAL: standardized issuer trust lists [#vical-standardized-issuer-trust-lists] **Serves:** Trusted Issuer Lists. A [VICAL](/docs/digital-trust-service/vical-overview) (Verified Issuer Certificate Authority List) is a mechanism defined in the [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) standard to support establishing trust in networks where relying parties need to verify credentials issued by numerous different issuers. The VICAL operator collects and validates IACAs from different issuing authorities, then cryptographically signs them into a single list that relying parties can consume. VICAL is the concrete, standards-based implementation of a [Trusted Issuer List](/docs/digital-trust-service/trusted-lists#trusted-issuer-lists). See the [VICAL overview](/docs/digital-trust-service/vical-overview), [guide](/docs/digital-trust-service/vical-guide), and [consumption](/docs/digital-trust-service/vical-consumption) pages to implement it. ## RICAL: reader trust lists [#rical-reader-trust-lists] **Serves:** Trusted Reader Lists. A [RICAL](/docs/digital-trust-service/rical-overview) (Reader Certificate Authority List) is the mechanism for publishing which readers (verifiers) are authorized to request credentials. Holders and wallets consume a RICAL to confirm that a verifier is trusted before presenting data to it. RICAL is the concrete implementation of a [Trusted Reader List](/docs/digital-trust-service/trusted-lists#trusted-reader-lists). See the [RICAL overview](/docs/digital-trust-service/rical-overview), [guide](/docs/digital-trust-service/rical-guide), and [consumption](/docs/digital-trust-service/rical-consumption) pages to implement it. ## Choosing an option [#choosing-an-option] | You want to publish or consume | Category | Use | | -------------------------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------- | | Which participants may issue or verify a credential type | Issuer and Reader lists | [Ecosystem policies via MATTR VII APIs](#ecosystem-policies-via-mattr-vii-apis) | | A signed list of trusted issuer certificate authorities | Issuer lists | [VICAL](#vical-standardized-issuer-trust-lists) | | A list of trusted readers (verifiers) | Reader lists | [RICAL](#rical-reader-trust-lists) | | Certified wallets and technologies | Trusted wallets | [Wallet attestation](/docs/issuance/credential-issuance/wallet-attestation) and trust framework criteria | Once your application consumes trust information from a DTS, verification and presentation processes are greatly simplified. Establishing trust with every individual issuer or verifier is delegated to the DTS operator. # Overview URL: /docs/digital-trust-service ## Introduction [#introduction] Trust networks are created to enable participants (governments, organizations, businesses, end users, etc.) to exchange data and value in different digital interactions. They can be very large or very small, ranging from countries and government agencies to enterprises and small companies. **Examples of trust networks** Banks, fintech companies, and third-party providers collaborate to securely share financial data and services with user consent. A typical use case involves a consumer using a budgeting app to access and aggregate their financial data from multiple bank accounts. The app requests access to the user's bank accounts through secure APIs, and with the user’s approval, retrieves transaction data to help them manage spending and savings, all within a trusted network. In the EU eIDAS, cross-border trust is established for digital identities and signatures, enabling individuals and businesses in EU member states to engage in secure electronic transactions. A practical use States issue digital versions of driver’s licenses, allowing citizens to prove their identity securely via mobile devices. A typical use case involves a citizen using their mDL to verify their age when purchasing alcohol at a store. The store clerk scans the digital license via a secure reader, instantly confirming the individual’s age and identity without the need for physical ID, making the transaction faster and more secure. ## Challenge [#challenge] These trust networks can include numerous issuers, holders and verifiers interacting in multiple ways and exchanging various verifiable credentials. While verifiable credentials offer some privacy preserving and tamper evident properties to enhance trust, all network participants can benefit from additional trust layers that make it easier to understand which participants and what interactions can be trusted. Furthermore, as the number of participants increases, it becomes increasingly difficult to establish and maintain direct trust relationships with each one individually. This complexity can lead to security vulnerabilities, poor user experiences and a breakdown in trust. **Examples of challenges in trust networks** Security challenges arise when an uncertified and possibly malicious third-party provider attempts to gain access to user data. Without proper trust layers, these providers may expose vulnerabilities that hackers can exploit, leading to data breaches. From a user experience perspective, if consumers cannot easily identify which third-party apps are trustworthy, they might hesitate to use new services, creating friction and frustration. Security challenges occur when identity credentials issued in one country are not properly validated or if a foreign provider does not meet the same security standards, creating an opening for identity fraud or manipulation. From a user experience perspective, individuals attempting to use their eID in another country may encounter long delays or service denials if the identity verification process is not interoperable or aligned. For instance, a German citizen trying to sign a contract in Italy might face an unresponsive or overly complex process, leading to frustration and a lack of confidence in the system. Security challenges arise if one state’s mDL system is less secure than another’s, which could allow counterfeit or manipulated digital licenses to pass unnoticed, leading to fraud or misuse. For the user experience, travelers from one state may face difficulties if their mDL is not recognized by systems in another state, such as at an airport or retail store. This could result in delays, rejections, or the need to resort to physical IDs, creating an inconvenient and inconsistent user experience. ## Digital Trust Service (DTS) [#digital-trust-service-dts] The purpose of a **Digital Trust Service (DTS)** is to address this growing challenge by enabling participants to rely on a single trusted framework. This simplifies interactions, as participants need to establish trust only with the DTS itself rather than with each individual entity. It is the network operator's responsibility to establish and maintain trust in different digital entities, and then use a DTS to reflect that trust to all other participants. This approach fosters a more scalable and cohesive network, where participants can confidently engage in digital interactions, knowing that the DTS safeguards their interests. This enables participants to enhance their operations, contributes to economic growth, and improves the user experience, all while fostering a more secure and trusted digital environment. DTSs enable scaling networks by incorporating **direct** and **proxy** trust relationships: * **Direct trust**: In a direct trust model, participants establish trust relationships directly with one another, as each party verifies and trusts the identity and security of the other participant independently. This typically involves exchanging unique public keys directly between participants:
Single Direct Trust Relationship
A real-world analogy for this would be hosting a dinner party where every guest needs to personally introduce themselves to every other guest to establish trust. Each person has to explain who they are, verify their background, and remember everyone else’s information. While this can work in a small group, it becomes increasingly challenging as the number of guests (or participants) grows. Managing these direct trust relationships—like exchanging public keys—becomes complex and cumbersome as every individual must retrieve, exchange, and manage multiple keys: Multiple Direct Trust Relationships * **Proxy trust**: In contrast, a proxy trust model allows participants to rely on a third-party intermediary, such as a DTS, to establish and manage trust on their behalf. Instead of verifying other participants directly, entities trust the DTS to handle trust relationships. The DTS operator manages all keys and links them to unique identifiers assigned to each participant:
Single Proxy Trust Relationship
This can be compared to the same dinner party but with a trusted host acting as an intermediary. The host has already vetted and verified every guest, ensuring they are trustworthy. Guests no longer need to introduce themselves to each other individually—they simply trust the host’s judgment. If new guests arrive, the host ensures they are introduced to everyone, making interactions seamless and scalable: Multiple Proxy Trust Relationships By leveraging proxy trust through a DTS, participants can benefit from a simpler, more scalable trust framework. The DTS removes the need to establish individual trust relationships, streamlining interactions and creating a secure and trusted digital environment. **Examples of digital trust services** The purpose of the DTS is to provide a unified set of standards and regulations, enforced by the Financial Conduct Authority (FCA), so that consumers can confidently use third-party applications to access their bank accounts and financial data. The DTS enables cross-border recognition of digital identities, so that a citizen from Germany can use their national eID to securely access services in France, without needing to establish trust with each individual service provider in different countries. The purpose of the DTS is to create a trusted digital identity system that can be securely used across states and in a variety of in-person and remote interactions. ## Trust framework [#trust-framework] Trust networks are built on a legal foundation that establishes the rules and regulations governing the network. These laws ensure compliance with data protection, security, and privacy requirements, providing a trust framework for network participants to rely upon: * **Policies**: Define how participants can operate and interact. Policies are expressed as [trusted lists](/docs/digital-trust-service/trusted-lists): a trusted issuers list (who may issue), a trusted readers list (who may request), and trusted wallets and technologies (which certified applications may participate). See [Trusted Lists](/docs/digital-trust-service/trusted-lists) for a full explanation of each category. * **Recognized standards**: Defined and enforced set of recognized standards that ensure uniformity and interoperability. These may include local standards such as the [Trusted Digital Identity Framework (TDIF)](https://architecture.digital.gov.au/trusted-digital-identity-framework-tdif-0) in Australia or the [Digital Identity Services Trust Framework](https://www.digital.govt.nz/standards-and-guidance/identity/trust-framework) in New Zealand, as well as global standards like [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) for mobile driver’s licenses (mDLs). * **Schemes and vocabularies**: Defined schemes and vocabularies for the content and structure of supported verifiable credentials. These may include custom credential profiles created for the trust network, as well as subsets of global or industry-specific standards. **Examples of trust frameworks** The trust framework includes certified applications, banks, and third-party providers (TPPs). Consumers can trust apps that are approved by the Open Banking Implementation Entity (OBIE) to securely access their financial data, while banks and TPPs, regulated by the Financial Conduct Authority (FCA), must follow strict data protection and strong customer authentication protocols to ensure only authorized parties can access or initiate payments on behalf of customers. Trust is built through Qualified Trust Service Providers (QTSPs), national eID systems, and cross-border electronic signatures. Citizens can trust their national eID to securely access services across EU member states, while electronic signatures and seals, issued by QTSPs, are legally recognized and ensure secure transactions, such as signing contracts or accessing government services. The trust framework includes state-issued mobile driver's licenses (mDLs), certified verification applications, and trusted verifiers such as law enforcement and retailers. Only apps and verifiers adhering to the ISO/IEC 18013-5 standard can securely authenticate a user’s mDL, ensuring that interactions like age verification or airport check-ins are both secure and compliant with state and national standards. ## Operation [#operation] The trust network operator acts as an *accreditation body*, responsible for overseeing the accreditation process, assessing compliance, and granting certifications to qualifying participants. This is based on an *accreditation framework*, a structured evaluation, ensuring all participants meet predefined standards. *Onboarding and Offboarding mechanisms* facilitate the seamless entry and exit of participants within the trust network, ensuring continuous adherence to standards and policies. Continuous monitoring and auditing processes should be in place to ensure that participants adhere to the established policies and standards. This oversight helps maintain trust by detecting and addressing potential issues or breaches. **Examples for operating a DTS** Operation is managed by the Open Banking Implementation Entity (OBIE), which enforces compliance with the UK’s Open Banking Standard. This includes overseeing the API ecosystem and certifying third-party providers that wish to access bank customer data securely. Operation is managed by national authorities that accredit Qualified Trust Service Providers (QTSPs). These providers issue eID credentials, digital signatures, and time stamps that are legally binding and recognized across all EU member states. State Departments of Motor Vehicles (DMVs) are responsible for issuing and managing mobile driver's licenses (mDLs) through secure apps. The American Association of Motor Vehicle Administrators (AAMVA) plays a central role in setting standards and guidelines for mDL interoperability, ensuring that mDLs are recognized across different states. AAMVA also helps certify and authenticate verifiers and relying parties, such as law enforcement agencies, retailers, and TSA, ensuring they meet the required security standards to verify mDLs. This ensures that all parties, including verifiers, are trusted to handle sensitive identity information securely and consistently. ## Consuming [#consuming] There are different models for a DTS to make the trust framework available for consumption. These range from public and authenticated APIs that expose ecosystem policies to standardized mechanisms such as VICAL for issuer trust lists and RICAL for reader trust lists. See [DTS options](/docs/digital-trust-service/consuming-trust-overview) for the full menu and guidance on choosing between them. **Examples for consuming trust frameworks** Consumers use third-party apps that connect to their bank accounts to view financial data or make transactions. The apps rely on secure APIs and customer authentication processes that ensure data is protected and accessible only with the user’s consent. Users consume trust services by using their national digital identity to access public services or sign legal documents in another EU country. Service providers verify these eIDs through the eIDAS trust framework, ensuring they meet the required standards for cross-border transactions. Citizens use their mDLs to verify their identity in various scenarios, such as when boarding a flight or purchasing age-restricted products. The verification process involves scanning a QR code or using an NFC reader to ensure the mDL is authentic and has not been tampered with. ## Trust marks [#trust-marks] Retrieved information can be displayed to participants using **trust marks**. These are digital indicators that signify the security, authenticity, and trustworthiness of online interactions, services, or entities. Trust marks can take the form of digital badges, seals, or certificates that are prominently displayed on websites, applications, or digital credentials. They provide assurance to users that the entity displaying the trust mark has been verified and complies with stringent security standards and best practices. When identifying a trust mark, users gain confidence that their interactions are secure and that their data is being handled responsibly. In essence, trust marks act as visual endorsements of trustworthiness, making it easier for users to recognize and choose secure and reputable services. **Examples for trust marks** Consumers can see trust information when they connect to third-party apps. These apps can display trust marks, such as the FCA logo or Open Banking certification, indicating that they are certified and authorized to securely access the user's financial data. This gives users confidence that their data is being shared with a trusted party. Users can view trust information when they sign documents or authenticate themselves online. For example, when a citizen uses their eID to sign a contract or access a cross-border service, they can be shown trust marks or seals from a Qualified Trust Service Provider (QTSP). These marks indicate that the transaction is legally recognized and secure across all EU member states. Trust information is displayed when users present their mDL for verification. At a checkpoint, such as at a store or airport, the verification system can show a confirmation screen or a trust mark indicating that the verifier (e.g., TSA or retailer) is certified to accept and authenticate mDLs according to the ISO/IEC 18013-5 standard. This provides assurance that the user's digital ID is being handled securely by a trusted party. ## MATTR capabilities [#mattr-capabilities] MATTR platforms enable customers to effectively operate trust networks by offering the following key features: * **Network operation**: MATTR VII [ecosystem capabilities](/docs/digital-trust-service) allow network operators to: * Onboard entities as participants, and associate each participant with a unique [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) or [DID](/docs/concepts/dids) to identify them. * Configure valid credential types, and associate each credential type with a unique combination of `format` and `docType`. * Create a policy that defines what what *credential types* (identified by their **format** and **type/docType**) can be **issued** and/or **verified** by what *participants* (identified by their **IACA/DID**). * Make this information available for issuers, holders and verifiers to consume and integrate into their solutions using either: * [MATTR VII APIs](/docs/api-reference/platform/policy/getLatestEcosystemPolicy). * MATTR Pi [Holder](/docs/holding/sdk-overview), [Verifier Web](/docs/verification/remote-web-verifiers/sdks/overview) and [Verifier Mobile](/docs/verification/remote-mobile-verifiers/sdks/overview) SDKs. * MATTR [GO Hold](/docs/holding/go-hold/getting-started) and/or [GO Verify](/docs/verification/go-verify/getting-started). * A [VICAL](/docs/digital-trust-service/vical-overview). * **Credential issuance**: MATTR VII tenants can be used to [issue](/docs/issuance) and manage verifiable credentials. When a MATTR VII tenant is onboarded into a network, holders and verifiers that trust the trust network can also trust credentials issued by this tenant without establishing a direct trust relationship with it. * **Credential holding**: [MATTR Pi](/docs/holding/sdk-overview) and [MATTR GO](/docs/holding/go-hold/getting-started) platforms enable creating digital applications that allow users to claim, store, and present credentials. These platforms can consume and display information from DTSs to enable holders easily establish trust with issuers and verifiers they are interacting with. * **Credential verification**: All MATTR platforms offer credential [verification capabilities](/docs/verification). Once a verification application consumes trust information from a DTS, verification processes are greatly simplified, as establishing trust with every single issuer is delegated to the DTS operator. ## Example use cases [#example-use-cases] National network that connect banks, fintech companies, and payment processors across the country in a unified, trusted network. * Network operation (MATTR VII): MATTR VII is used to manage the trust network, onboarding banks, fintech providers, and payment processors. By leveraging the DTS, participants onboarding processes are simplified, ensuring all entities meet strict security and compliance standards. * Credential issuance (MATTR VII): When a financial institution is onboarded into the network, it can use MATTR VII to issue trusted digital credentials, such as loan approvals, account statements, or credit scores. Since all participants trust the trust framework, there’s no need to establish individual relationships between issuers and verifiers, making the process faster and more efficient. * Credential holding (MATTR Pi/GO): Service providers can offer customers MATTR Pi or GO wallets to store and manage their digital financial credentials. A key feature is that these wallets notify users when an accredited verifier—such as a bank, loan provider, or financial advisor—requests access to a specific credential. This transparency builds trust between users and verifiers, ensuring that users only share their financial information with trusted entities. * Credential verification (MATTR VII/Pi/GO): Service providers use different MATTR verification capabilities to authenticate digital credentials, such as loan approvals or bank statements. The DTS centralizes trust management, so verification is fast and seamless. These capabilities significantly enhance the security of financial transactions by ensuring that all participants adhere to strict standards for credential issuance and verification. Customers are notified when accredited verifiers request access to their credentials, giving them full control and confidence in how their sensitive financial information is shared. This transparency, combined with the streamlined processes, improves the overall user experience, reducing friction in financial interactions and encouraging more frequent use of digital services. As a result, higher customer engagement translates to increased transaction volumes and revenue growth for banks and fintech companies, while the improved security reduces the risk of fraud and data breaches. Government-led initiatives aimed at issuing and managing digital identities for citizens, allowing them to securely access public services such as healthcare, education, and social benefits. * Network operation (MATTR VII): MATTR VII enables onboarding government agencies, service providers, and private organizations into the trust network, allowing them to issue and verify digital identity credentials. The streamlined onboarding process ensures that all participants meet strict security standards, building a trusted network where users can confidently access public and private services. * Credential issuance (MATTR VII): Government agencies use MATTR VII to issue digital credentials like national ID cards, driving licenses, or health insurance numbers. Because the trust is governed by the DTS, service providers and citizens trust these credentials without needing to establish direct relationships with each agency. * Credential holding (MATTR Pi/GO): Government agencies can offer citizens MATTR Pi or GO wallets to securely store their digital credentials. These wallets enhance user trust by notifying citizens when an accredited verifier – such as a healthcare provider, government office, or private organization – requests access to their credentials. This transparency ensures that citizens feel in control of their personal data, only sharing it with trusted entities. * Credential verification (MATTR platforms): Government services and private organizations use different MATTR verification capabilities to authenticate digital credentials. Because the DTS manages trust, verifiers can quickly and easily authenticate credentials, improving service delivery times. This trust network enhances the security of digital identities by centralizing trust management and ensuring that credentials are issued and verified within a secure trust network. Citizens benefit from the transparency of being notified when accredited verifiers request access to their digital IDs, which protects them from unauthorized access and misuse of their personal information. The improved security fosters trust in the system, encouraging wider adoption of digital IDs and more frequent interactions between citizens and government services. This, in turn, leads to cost savings and operational efficiency for the government, while improving the overall delivery of public services. Consortiums of international airlines, airports, and transportation authorities seeking to streamline identity verification for travelers, replacing physical documents like passports and boarding passes with secure digital credentials. * Network operation (MATTR VII): MATTR VII is used to onboard airlines, airports, and border agencies into the trust network. The DTS ensures that participants adhere to the consortium's requirements, creating a seamless and secure experience for travelers. * Credential issuance (MATTR VII): Airlines and border control authorities issue trusted travel credentials—such as digital boarding passes and visas—using MATTR VII. The DTS ensures that these credentials are trusted across the network, so travelers can move through checkpoints more quickly and securely. * Credential holding (MATTR Pi/GO): Service providers can offer customers MATTR Pi or GO wallets to store their digital travel documents. These wallets not only store credentials but also notify travelers when an accredited verifier, such as a customs officer, airline, or airport security checkpoint, requests access to a document like a boarding pass or visa. This transparency ensures that travelers know who is requesting their data, fostering trust and confidence in the digital travel process. * Credential verification (MATTR platforms): Airports, airlines, and customs agencies use different MATTR verification capabilities to authenticate travel credentials. The DTS handles all trust management, simplifying the verification process and enabling fast, secure checks at checkpoints. This ensures that all digital travel credentials are issued and verified securely, significantly improving the security of the travel process. The improved security, along with the transparency of credential management, enhances traveler confidence and encourages the adoption of digital travel documents. This leads to faster processing at airports and borders, increasing passenger throughput and driving revenue growth for airlines and airports, while also strengthening security in global travel operations. Global shipping authorities are responsible for verifying shipping documentation, such as certificates of origin, compliance documents, and customs declarations, across international trade routes. * Network operation (MATTR VII): MATTR VII enables onboarding customs authorities, ports, and logistics companies into the trust network. The DTS governs the issuance and verification of shipping documents, ensuring all parties adhere to standardized, trusted processes. The improved trust framework leads to more efficient supply chain operations. * Credential issuance (MATTR VII): Customs authorities and shipping companies issue trusted verifiable credentials (e.g., customs declarations, compliance certificates) using MATTR VII. Since these credentials are backed by the DTS, they are trusted across all participants in the supply chain, without requiring direct relationships between issuers and verifiers. * Credential holding (MATTR Pi/GO): Service providers can offer shipping companies and logistics operators MATTR Pi or GO wallets to store and manage their shipping documents. These wallets notify users when an accredited verifier—such as a customs officer or port authority—requests access to a specific shipping document. This transparency reassures shipping operators that only trusted entities can access their documents, improving trust and efficiency in document management. * Credential verification (MATTR platforms): Customs officers and port authorities use different MATTR verification capabilities to authenticate shipping credentials. The DTS handles the trust framework, ensuring that verifiers can instantly confirm the validity of documents without needing to manually validate each issuer. This streamlines customs clearance and port operations, allowing for faster shipment processing. These capabilities improve the security of shipping operations by ensuring that all documents are issued and verified within a trusted and secure framework. This reduces the risk of unauthorized document access or tampering, leading to more secure and reliable operations. As a result, the enhanced security, along with faster processing times, increases the number of shipments that can be handled, driving revenue growth for logistics companies and port operators, while also reducing the risk of fraud or regulatory non-compliance. ## Underlying platforms [#underlying-platforms] MATTR DTS capabilities MATTR DTS capabilities are configured and operated using MATTR VII, either via the MATTR Portal or via API requests. # Privacy in digital trust services URL: /docs/digital-trust-service/privacy Description: How a Digital Trust Service publishes trust information without observing individual verifications, and how that preserves the privacy properties of a decentralized credential ecosystem. A [Digital Trust Service](/docs/digital-trust-service) (DTS) lets the operator of a trust network publish information that participants need to evaluate trust: which issuers are accredited, which verifiers can request which credentials, and what credential types are recognized in the ecosystem. This page describes the privacy properties of a DTS, why it does not introduce a central point of surveillance, and how MATTR's DTS capability is designed to preserve the decentralized model. For the broader picture, see [Privacy in MATTR's architecture](/docs/concepts/privacy). ## A DTS is a publication mechanism, not a transaction broker [#a-dts-is-a-publication-mechanism-not-a-transaction-broker] The most important privacy property of a DTS is the role it plays in the architecture. A DTS: * Publishes trust information so that issuers, holders, and verifiers can evaluate trust locally. * Does not sit on the transaction path between holder and verifier. * Does not see individual presentations. This is what makes proxy trust (where every participant trusts the DTS) compatible with the decentralized model. The DTS replaces the manual work of negotiating bilateral trust between every pair of participants, without becoming an intermediary in every verification interaction. Compare this to a federated identity provider, where the intermediary has to see every authentication request in order to issue an assertion. A DTS does not have that property. Once a verifier has fetched the trust list, it can verify credentials without ever talking to the DTS again. ## What is in a trust list [#what-is-in-a-trust-list] Trust lists published by a DTS contain entries that identify participants and the role they play in the ecosystem. Typical contents include: * Trusted issuer entries (often anchored to an [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca)). * Trusted verifier entries, where the ecosystem requires accredited verifiers. * Recognized credential types and their associated formats. * Cryptographic signatures by the DTS operator over the published lists, so that consumers can verify the lists' integrity. Trust lists do not contain personal information about credential holders. They are about the participants in the trust network, not about the people whose data flows through the network. For ecosystems that consume trust information as a VICAL (defined in [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html)), the VICAL is similarly a list of trusted issuer certificate authorities, signed by the VICAL operator. It does not contain holder data. ## How participants consume trust information [#how-participants-consume-trust-information] MATTR's DTS capability supports several consumption patterns. In all of them, the consumer fetches trust information independently of any specific verification: * **MATTR VII APIs:** The trust network operator publishes ecosystem policies that define participants and the credential types they can issue or verify. Issuers, holders, and verifiers can fetch these policies via API. * **MATTR Pi and MATTR GO SDK integrations:** Holder and verifier SDKs can consume ecosystem information and apply it to the local presentation flow. * **VICAL:** For ecosystems that follow the ISO/IEC 18013-5 model, the operator can publish a signed VICAL that relying parties consume on a schedule. In every pattern, the fetch is asynchronous and is not associated with a specific verification. The DTS sees that some participant fetched a list. It does not see what verification activity, if any, that fetch was related to. ## What MATTR's DTS capability does NOT do [#what-mattrs-dts-capability-does-not-do] The MATTR VII DTS capability is designed as a publication mechanism. It explicitly does not: * Receive verification events from participants. * Maintain a per-holder ledger of credentials or presentations. * Sit in the transaction path between holder and verifier at presentation time. If a customer operating a DTS chooses to layer additional ecosystem-level analytics or reporting on top, that is a separate system that the operator is responsible for designing and disclosing. The core DTS capability does not introduce a central surveillance point. ## Trust frameworks and the rules around DTS operation [#trust-frameworks-and-the-rules-around-dts-operation] A DTS does not operate in a vacuum. It is the technical expression of a trust framework: the governance rules that define how participants are accredited, what claims credentials may carry, and what obligations sit on issuers, holders, and verifiers. Many trust frameworks explicitly prohibit issuers, wallets, and trust framework providers from tracking or correlating verification activity. Where this applies, the DTS operator's job is to design the service so that compliance is enforced architecturally rather than depending on each participant's good behavior. MATTR's DTS capability supports this by keeping the DTS out of the verification path. See [Decentralized trust model](/docs/concepts/decentralized-trust-model) for a fuller treatment of how governance instruments need to change as the architecture changes. ## Practical guidance for DTS operators [#practical-guidance-for-dts-operators] If you are operating a DTS with MATTR VII, the following principles help preserve the architecture's privacy properties: * Publish trust information in a form that is consumable without identifying the consumer. Authenticated APIs are fine, but the authentication should identify the consuming organization, not individual transactions. * Sign published lists. Consumers should be able to verify the integrity of the trust information independently of the transport. * Define a clear policy for what the DTS may and may not log about consumption. Most operators benefit from aggregate consumption metrics; per-participant transactional logs are rarely needed and easily abused. * Make trust framework obligations explicit. Issuers, holders, and verifiers should know what they are signing up to when they join the ecosystem. * Choose the regional MATTR deployment that matches the data residency expectations of the ecosystem you are operating. ## Frequently asked questions [#frequently-asked-questions] ### Does a Digital Trust Service see individual credential presentations? [#does-a-digital-trust-service-see-individual-credential-presentations] No. A DTS publishes trust information (lists of trusted issuers, verifiers, and credential types) that participants consume independently. It is not on the transaction path between holder and verifier, and it does not see who is presenting what to whom. ### What does a trust list contain? [#what-does-a-trust-list-contain] A trust list contains entries that identify participants in the trust network (typically by IACA, DID, or signer certificate) and the credential types they are authorized to issue or verify. It does not contain personal information about credential holders. ### Why use a DTS instead of direct trust relationships? [#why-use-a-dts-instead-of-direct-trust-relationships] Direct trust relationships are workable in small ecosystems but do not scale. A DTS lets every participant rely on a single trust anchor maintained by the network operator, rather than negotiating bilateral trust with every other participant. This keeps the architecture decentralized without requiring every party to vet every other party. ### Can a DTS be used to log verifications across an ecosystem? [#can-a-dts-be-used-to-log-verifications-across-an-ecosystem] No. MATTR's DTS capability is designed as a publication mechanism and does not receive verification events from participants. Ecosystem-level verification logging would require a separate reporting or analytics system layered outside the DTS, which the customer would be responsible for designing and disclosing. Doing so is also typically prohibited by the trust framework rules that govern the DTS, as many trust frameworks explicitly prevent the DTS from collecting verification activity. ### How do verifiers consume trust information from a MATTR DTS? [#how-do-verifiers-consume-trust-information-from-a-mattr-dts] Trust information can be consumed via the MATTR VII Ecosystem APIs, via MATTR Pi and MATTR GO SDK integrations, or as a VICAL (a signed Verified Issuer Certificate Authority List defined in ISO/IEC 18013-5). The consumption is asynchronous and does not identify the verification activity that triggered it. ## Related reading [#related-reading] * [Privacy in MATTR's architecture](/docs/concepts/privacy) * [Decentralized trust model](/docs/concepts/decentralized-trust-model) * [Digital Trust Service overview](/docs/digital-trust-service) * [VICAL](/docs/digital-trust-service/vical-overview) * [Chain of trust](/docs/concepts/chain-of-trust) # How to consume a RICAL as a wallet provider URL: /docs/digital-trust-service/rical-consumption Once a RICAL provider has published a RICAL, wallets need to retrieve it, verify its authenticity, and load the trusted Reader Root Certificates into their verification logic. The steps below describe how to do that against a RICAL published by a MATTR-hosted ecosystem. ISO/IEC 18013-5 uses the term **reader** for the entity that requests data from an mDoc. Throughout this documentation we use **verifier** or **relying party** for the same role. Certificate names defined by the standard (for example, *Reader Root Certificate*) keep their ISO terminology. ### Retrieve the RICAL provider's root CA certificate [#retrieve-the-rical-providers-root-ca-certificate] The RICAL provider and the VICAL provider share the same root CA distribution endpoint. Call the public DTS root CA certificates endpoint to obtain the root certificate(s) used by the RICAL provider as the anchor of its signing chain. The endpoint is public and does not require authentication. ```bash curl https://your-tenant.vii.au01.mattr.global/v1/ecosystems/public/certificates/ca ``` This endpoint is public and does not require authentication. It should be provided by the RICAL provider to all wallets in the ecosystem. The response includes one or more PEM-encoded root certificates. Persist these certificates as your trust anchor for the RICAL provider. They only need to be refreshed when the provider rotates its root. ### Retrieve the latest RICAL [#retrieve-the-latest-rical] Call the [Retrieve latest RICAL](/docs/api-reference/platform/rical/getLatestRical) endpoint to download the current RICAL. ```bash curl -o rical-latest.cbor \ https://your-tenant.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals/public/latest ``` This endpoint is public and does not require authentication. It should be provided by the RICAL provider to all wallets in the ecosystem. The response is a binary CBOR file (`application/cbor`) encoded as a [COSE\_Sign1](https://datatracker.ietf.org/doc/html/rfc9052) structure, following the same packaging conventions as a VICAL. Wallets should poll the latest endpoint on a schedule that matches the RICAL provider's publishing cadence so they always operate against the latest set of trusted verifiers. ### Validate the RICAL signature and certificate chain [#validate-the-rical-signature-and-certificate-chain] The RICAL is signed by a RICAL Signer certificate, which itself chains back to the RICAL provider's root CA retrieved in step 1. Before trusting any data in the RICAL, perform the following checks: 1. Extract the RICAL Signer certificate from the COSE\_Sign1 unprotected header (`x5chain`, COSE label `33`). 2. Verify the COSE\_Sign1 signature using the public key from that signer certificate and the signature algorithm declared in the protected header (for example, ES256 / COSE algorithm `-7`). 3. Build the certificate chain from the signer certificate up to the root CA from step 1, and validate each link: signature, validity period, key usage, and revocation status (CRL). 4. Confirm the root of the chain matches one of the trust-anchor certificates you persisted in step 1. If any of these checks fail, reject the RICAL and continue using the previously trusted version. ### Decode the RICAL payload [#decode-the-rical-payload] Once the signature has been verified, decode the COSE\_Sign1 payload to access the RICAL data. The decoded structure follows the CDDL definition in the [RICAL overview](/docs/digital-trust-service/rical-overview#rical-structure). The `certificateInfos` array contains one entry per trusted Reader Root Certificate, each carrying the DER-encoded certificate, its serial number, and its Subject Key Identifier. ### Load the trusted Reader Root Certificates into your wallet [#load-the-trusted-reader-root-certificates-into-your-wallet] Iterate over `payload.certificateInfos` and extract each `certificate`. These are the trust anchors you should use when validating signed verifier requests: ```javascript import { decode } from "cbor-x"; const ricalBytes = await fetch( "https://your-tenant.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals/public/latest" ).then((r) => r.arrayBuffer()); // COSE_Sign1 structure: [protected, unprotected, payload, signature] const coseSign1 = decode(new Uint8Array(ricalBytes)); const payload = decode(coseSign1[2]); const trustedReaderRoots = payload.certificateInfos.map((info) => ({ certificateDer: info.certificate, serialNumber: info.serialNumber, ski: info.ski, })); ``` When evaluating a verifier request: 1. Extract the verifier's certificate chain from the signed request. 2. Build the chain up to a Reader Root Certificate and confirm that root matches one of the trusted Reader Root Certificates loaded from the RICAL (for example, by comparing SKI or by direct certificate match). 3. Validate the full PKI chain from the verifier's leaf certificate up to that trusted root, including validity periods, key usage, and revocation status. This mechanism lets a wallet authenticate verifiers across the ecosystem without maintaining individual trust relationships with each relying party. # Learn how to set up a RICAL URL: /docs/digital-trust-service/rical-guide ## Introduction [#introduction] The purpose of a [Reader Identity Certificate Authority List (RICAL)](/docs/digital-trust-service/rical-overview) is to enable holder applications (wallets) in a digital ecosystem to verify the identity of relying parties (verifiers) from a single authoritative source. It is the counterpart to a [VICAL](/docs/digital-trust-service/vical-overview): where a VICAL distributes trusted *issuer* roots, a RICAL distributes trusted *verifier* roots. ISO/IEC 18013-5 uses the term **reader** for the entity that requests data from an mDoc. Throughout this documentation we use **verifier** or **relying party** for the same role. Certificate names defined by the standard (for example, *Reader Root Certificate*) keep their ISO terminology. This guide will walk you through setting up a RICAL and publishing a list of trusted Reader Root Certificates. Each step can be completed either through the [Portal](/docs/platform-management/portal) or via the MATTR VII API. Select the tab that matches how you want to work. ## Prerequisites [#prerequisites] * Make sure you understand the concepts of a [RICAL](/docs/digital-trust-service/rical-overview) and how it relates to a [Digital Trust Service (DTS)](/docs/digital-trust-service). * You need access to an existing MATTR VII tenant with either the `DTS Provider` or `Admin` role. Refer to the [Getting started with the Portal](/docs/platform-management/portal#getting-started) tutorial to learn how to create a tenant and assign roles. * To use the API, you also need to [obtain a bearer access token](/docs/api-reference#obtain-an-access-token) to include in the `Authorization` header of each request. ## Guide overview [#guide-overview] Publishing a RICAL comprises the following steps: 1. [Create an Ecosystem](#create-an-ecosystem): This is the overarching entity that holds participants together. Only required if you don't already have an ecosystem on your tenant. 2. [Create participants and add verifier certificates](#create-participants-and-add-verifier-certificates): These are the relying parties (verifiers) that will be part of the RICAL. Each participant includes a Reader Root Certificate that anchors its verifier requests. 3. [Set up the RICAL signing certificate chain](#set-up-the-rical-signing-certificate-chain): Establish the DTS root CA and RICAL Signer Certificate used to sign the RICAL, using either a 2-tier or 3-tier model. 4. [Manually publish a RICAL](#manually-publish-a-rical): Configure the RICAL provider, then generate and publish a RICAL that includes your trusted Reader Root Certificates. 5. [Configure RICAL auto-generation and publishing](#configure-rical-auto-generation-and-publishing-optional) (optional): Set the RICAL to automatically generate and publish on a daily or weekly schedule. 6. [View previously published RICALs](#view-previously-published-ricals-optional) (optional): Review the history of previously published RICALs and download their files. ### Create an Ecosystem [#create-an-ecosystem] Creating an Ecosystem is a one-time step per tenant. It is only required if you don't already have an ecosystem on your tenant. If you already have an ecosystem (for example, because you created one when setting up a [VICAL](/docs/digital-trust-service/vical-guide)), you will not see the option to create a new one (in the Portal) and should skip directly to the next step. A single ecosystem is shared across both your VICAL and RICAL. Perform the following steps to create an Ecosystem: 1. Log in to the [MATTR Portal](https://portal.mattr.global/). 2. Navigate to the **Ecosystem** page under the *Digital Trust Service* section. 3. Enter a name for your Ecosystem, such as "My Digital Trust Service". 4. Select the **Create** button. Make a request of the following structure to [create an ecosystem](/docs/api-reference/platform/ecosystems/createEcosystem): ```http filename:"Request" POST /v1/ecosystems ``` ```json filename:"Request body" { "name": "My Digital Trust Service" } ``` * `name` : This required parameter is the name used to identify your ecosystem. The response will include an `id` property, which is the unique identifier for the ecosystem. You will use this `ecosystemId` in subsequent requests to reference this ecosystem. ### Create participants and add verifier certificates [#create-participants-and-add-verifier-certificates] Participants in a RICAL are the relying parties (verifiers) whose Reader Root Certificates you want holders to trust. For each participant, you upload a Reader Root Certificate (referred to as a **Verifier CA** in MATTR VII) that anchors the chain used to sign that verifier's requests. Unlike an issuer participant in a VICAL, a verifier participant in a RICAL does **not** declare credential types (`docTypes`). A trusted Reader Root Certificate is treated as authoritative for the ecosystem rather than scoped to specific credential types. Authorization of what a verifier may request is conveyed separately through the verifier's signed request and the holder's consent flow. Perform the following steps to create a participant and add its verifier certificate: 1. Select the **Participants** page under the *Digital Trust Service* section (this page is only visible if you have an existing ecosystem. If you don't have an ecosystem, you will need to return to step 1 above and create it first). 2. Select the **Create new** button.\ The *Create participant* form appears, starting from Step 1 (*Details*). 3. Insert a meaningful *Name* for the participant (e.g. "Montcliff Police"). 4. Use the *Country* dropdown list to select the participant's country (optional). When selected, this value must match the *Country* value in the verifier certificate associated with this participant. 5. If you select a country, a *State or Province* dropdown list is displayed. You can use it to select the participant's state or province (optional). When selected, this value must match the `stateOrProvinceName` value in the verifier certificate associated with this participant. 6. Insert the participant's *Address* and *Phone number* (optional). 7. Use the *Status* radio button to set the participant as **Active**. 8. Select the **Next** button.\ You are directed to Step 2 (*Certificates*). 9. Select the **Create** button to create the participant. 10. Select the **Verifier certificates** tab. 11. Select the **Add new** button to add a verifier certificate for the participant.\ The *Add verifier certificate* form appears. 12. Paste/upload the PEM-encoded Reader Root Certificate into the **Certificate PEM file** field. 13. Use the *Status* radio button to set the certificate to **Active**. 14. *(Optional)* To maintain trust continuity when rotating a participant's Reader Root Certificate, expand the **Link Certificate** section and add this certificate as a successor to a previously uploaded certificate (refer to [Linked certificates](/docs/digital-trust-service/rical-overview#linked-certificates)): * Use the *Predecessor certificate* dropdown to select the previous certificate that this new one succeeds. The dropdown lists the participant's existing certificates because the predecessor must already exist on your tenant before you can link to it. * Upload the link certificate into the *Link Certificate PEM file* field. The link certificate is the participant's proof that they own both the predecessor and the new certificate, which is what lets you accept the new one while preserving trust in the previous one. This option is only available once at least one certificate has been added for the participant. 15. Select the **Add** button. Repeat the above steps for each relying party (verifier) you want to include in your RICAL. First, make a request of the following structure to [create a participant](/docs/api-reference/platform/participants/createEcosystemParticipant) and mark it as a verifier: ```http filename:"Request" POST /v1/ecosystems/{ecosystemId}/participants ``` * `ecosystemId` : Replace with the `id` value obtained when you created the ecosystem. ```json filename:"Request body" { "name": "Montcliff Police", "status": "Active", "isVerifier": true, "country": "US", "stateOrProvince": "US-XX", "identifiers": {} } ``` * `name` : This required parameter is a meaningful name used to identify the participant. * `status` : Set this to `Active` so the participant is included in the ecosystem policy and the RICAL. Only active participants are published. * `isVerifier` : Set this to `true` so the participant can act as a verifier in the ecosystem. * `country` / `stateOrProvince` : *Optional*. When provided, must match the corresponding values in the verifier certificate uploaded for this participant. * `identifiers` : This required parameter defines the participant's credential-format identifiers. For a verifier participant, pass an empty object (`{}`), as the Reader Root Certificate is added separately in the next request. The response includes the participant `id`. Then, make a request of the following structure to [create a verifier certificate](/docs/api-reference/platform/participants-verifier-certificates/createParticipantVerifierCertificate) for the participant. This is the Reader Root Certificate that will be included in the RICAL: ```http filename:"Request" POST /v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates ``` * `participantId` : Replace with the `id` value obtained when you created the participant. ```json filename:"Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICTDCCAfKgAwIBAgIKeKcjIBGvXfS/sjAKBggqhkjOPQQDAjAwMQswCQYDVQQG\r\nEwJVUzEhMB8GA1UEAwwYRXhhbXBsZSBWZXJpZmllciByb290IENBMB4XDTI1MDgy\r\nNzIxMzMxNFoXDTMwMDgyNjIxMzMxNFowMDELMAkGA1UEBhMCVVMxITAfBgNVBAMM\r\nGEV4YW1wbGUgVmVyaWZpZXIgcm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH\r\nA0IABNJHM5ZE+fpVn7b9WwjVBiOiZq9eNXq1JkNj/6ZLe+2GkaRY/WE2Xbg7yx++\r\nh3QEdX3sGKzGO7dygQALBe/4qEyjgfMwgfAwHQYDVR0OBBYEFK8ogqdUH2vZlC1y\r\nNf619a8fnx8KMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMHsG\r\nA1UdHwR0MHIwcKBuoGyGamh0dHBzOi8vbGVhcm4udmlpLmF1MDEubWF0dHIuZ2xv\r\nYmFsL3YyL3ByZXNlbnRhdGlvbnMvY2VydGlmaWNhdGVzL2Q3YzE3ODI4LThkMTgt\r\nNDYyZS1iNDk3LWNjNjI2NWM4ZmQxYi9jcmwwLgYDVR0SBCcwJYYjaHR0cHM6Ly9s\r\nZWFybi52aWkuYXUwMS5tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSAAwRQIhAOqD\r\n0DF3rohBitl5jAj6x1164uGGj6yAhF/eE4aJeGc+AiAgaUYHzobzaPEWd+jZOh/A\r\nq8WgVJ+8sLx9WdJDs9/shQ==\r\n-----END CERTIFICATE-----\r\n", "status": "Active" } ``` * `certificatePem` : This required parameter contains the PEM-encoded Reader Root Certificate. It becomes immutable once the certificate is created. * `status` : Set to `Active` to include this certificate in the RICAL. Note that, unlike an issuer certificate in a VICAL, a verifier certificate does not take a `docTypes` array. Repeat this request for each relying party (verifier) you want to include in your RICAL. ### Set up the RICAL signing certificate chain [#set-up-the-rical-signing-certificate-chain] Each RICAL must be signed by a RICAL Signer Certificate (RSC) that chains back to a DTS root CA via a [chain of trust](/docs/digital-trust-service/certificates-overview). This chain is what consuming wallets use as the trust anchor to validate the authenticity and integrity of the RICAL. If you already have an existing active DTS root CA that you want to use as the trust anchor for your RICAL, you can skip this step. MATTR VII supports both **managed** and **unmanaged (external)** DTS certificates. Select the option that matches how you want to manage your certificate infrastructure. With managed DTS certificates, MATTR VII provisions and maintains the DTS root CA and the signer certificates for you. You create and activate the DTS root CA, and MATTR VII automatically creates a signer (and its certificate) and uses it to sign trust lists as required. Managed DTS certificates always use the [2-tier model](/docs/digital-trust-service/certificates-overview#certificate-models-2-tier-and-3-tier). 1. Navigate to the **Certificates** page under the *Platform Management* section. 2. Select the **Create new** button.\ The *New certificate* form appears. 3. Use the *Type* dropdown list to select **DTS CA**. 4. Use the *Management method* radio button to select **MATTR managed**. 5. Enter a meaningful name in the *Organization* field to identify the organization operating the DTS. 6. Use the *Country* dropdown list to select the country where the organization is located. 7. Select the **Create** button.\ The DTS root CA is created in an inactive state. 8. Scroll down and use the *Status* radio button to select **Active**. 9. Select the **Update** button to activate the DTS root CA. Make a request of the following structure to [create a managed DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/createDtsCaCertificate): ```http filename:"Request" POST /v1/ecosystems/certificates/ca ``` ```json filename:"Request body" { "organisationName": "Example Inc.", "commonName": "Example DTS CA", "country": "US" } ``` * `organisationName` : This required parameter indicates the organization associated with the DTS root CA certificate. * `commonName` : This *optional* parameter indicates the common name of the DTS root CA certificate. If not provided and a [custom domain](/docs/platform-management/custom-domain-overview) is configured and verified, the custom domain is used followed by the words `DTS CA`. If no custom domain is configured, the tenant subdomain is used instead. * `country` : This *optional* parameter indicates the DTS provider's country. If not provided, a country is selected based on the region of the tenant subdomain cloud host. When specified, the value must be a valid [Alpha 2 country code](https://www.iso.org/glossary-for-iso-3166.html) as per [ISO 3166-1](https://www.iso.org/standard/72482.html). The response will include an `id` property identifying the managed DTS root CA. Make a request of the following structure to [update the managed DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/updateDtsCaCertificate) and activate it: ```http filename:"Request" PUT /v1/ecosystems/certificates/ca/{dtsCaCertificateId} ``` * `dtsCaCertificateId` : Replace with the `id` value obtained when you created the managed DTS root CA. ```json filename:"Request body" { "active": true } ``` Once a managed DTS root CA is activated, MATTR VII automatically creates and uses a signer to sign trust lists as required. With unmanaged (external) DTS certificates, you supply and maintain the full certificate chain. You generate the DTS root CA, issue and sign the RICAL Signer Certificates (RSCs), upload the root and each RSC to MATTR VII, and handle renewal and revocation. Select the tab for the certificate model you want to configure. For guidance on choosing a model, see [certificate models: 2-tier and 3-tier](/docs/digital-trust-service/certificates-overview#certificate-models-2-tier-and-3-tier). In the 2-tier model, the DTS root CA directly signs the RICAL Signer Certificate (RSC). **Generate a self-signed root certificate (DTS root CA)** Use your preferred cryptographic library or tool to generate a self-signed root certificate (DTS root CA). In the 2-tier model, this certificate directly signs the signer certificate. Ensure it meets the requirements specified in [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) and in the [certificate requirements](/docs/digital-trust-service/certificates-overview#certificate-requirements) section. When using unmanaged (external) certificates, the DTS provider assumes full responsibility for the secure management of the uploaded root certificates and all subordinate certificates. This includes ensuring the protection, proper issuance, and timely revocation of certificates under the uploaded root, as MATTR VII does not manage or monitor these certificates on the DTS provider's behalf. **Register the external DTS root CA certificate with MATTR VII** 1. Expand the **Platform Management** menu in the navigation panel on the left-hand side. 2. Click on **Certificates**. 3. Select **Create new**. 4. Use the *Type* dropdown to select **DTS CA**. 5. Use the *Management method* dropdown to select **Externally managed**. 6. Paste/upload the PEM-encoded DTS root CA certificate into the **Certificate PEM file** field.\ The certificate must meet the following requirements: * Valid * Not expired * Compliant with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) 7. Select **Create** to register the unmanaged DTS root CA certificate. The newly created unmanaged DTS root CA is created in an inactive state. You can only activate it after you create at least one signer associated with this DTS root CA. Make a request of the following structure to [create an unmanaged DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/createDtsCaCertificate): ```http filename:"Request" POST /v1/ecosystems/certificates/ca ``` ```json filename:"Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\n...\r\n-----END CERTIFICATE-----\r\n" } ``` * `certificatePem` : This required parameter contains the PEM-encoded DTS root CA certificate. The certificate must meet the following requirements: * Valid * Not expired * Compliant with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) The response will include an `id` property, which is a unique identifier for the unmanaged DTS root CA. This identifier will be used in subsequent operations to reference this unmanaged DTS root CA. **Create a RICAL Signer** Create a RICAL Signer under the DTS root CA. In the 2-tier model, the RICAL Signer references the DTS root CA directly. 1. On the detail page of the DTS root CA you just registered, scroll down to the **RSC - RICAL Signer Certificate** section and select **Add new**. 2. Select the **Create** button.\ MATTR VII creates a RICAL Signer in a *pending* state and generates a Certificate Signing Request (CSR) for it. Make a request of the following structure to [create a RICAL signer](/docs/api-reference/platform/rical-signers/createRicalSigner) that references the unmanaged DTS root CA: ```http filename:"Request" POST /v1/ecosystems/certificates/rical-signers ``` ```json filename:"Request body" { "caId": "080c670a-2e90-4023-b79f-b706e55e9bc6" } ``` * `caId` : Replace with the `id` of the unmanaged DTS root CA. The response includes the RICAL signer `id` and a `csrPem` (the Certificate Signing Request). **Generate and sign the RICAL Signer Certificate (RSC)** 1. Use the **Download** or **Copy** buttons in the **Step 1. Download the RSC Certificate Signing Request (CSR)** section of the RICAL Signer detail page to obtain the CSR. 2. Using your preferred cryptographic tool, generate and sign the RSC using the CSR from the previous step. In the 2-tier model, the RSC must be signed by the DTS root CA's private key. **Associate the RSC with the RICAL Signer** Upload the signed RSC to the RICAL Signer and activate it. 1. On the RICAL Signer detail page, under **Step 2. Upload signed RSC**, paste/upload the PEM-encoded RSC into the **Certificate PEM file** field. 2. Use the *Status* radio button to set the RICAL Signer to **Active**. 3. Select **Update** to associate the RSC and activate the RICAL Signer. Make a request of the following structure to [update the RICAL signer](/docs/api-reference/platform/rical-signers/updateRicalSigner) to associate the signed RSC and activate it: ```http filename:"Request" PUT /v1/ecosystems/certificates/rical-signers/{ricalSignerId} ``` * `ricalSignerId` : Replace with the RICAL signer `id` from the previous step. ```json filename:"Request body" { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIDXTCCAkWgAwIBAgIJAL5...\r\n-----END CERTIFICATE-----\r\n" } ``` * `active` : Set to `true`. A RICAL signer can only be activated once a valid `certificatePem` is provided. * `certificatePem` : The PEM-encoded RSC generated from the CSR. **Activate the DTS root CA** 1. Navigate back to the **Certificates** page in the MATTR Portal. 2. Select the DTS root CA you created in the first step. 3. Use the *Status* radio button to set the DTS root CA to **Active**. 4. Select **Update** to activate the DTS root CA. Make a request of the following structure to [update the unmanaged DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/updateDtsCaCertificate) and activate it: ```http filename:"Request" PUT /v1/ecosystems/certificates/ca/{dtsCaCertificateId} ``` * `dtsCaCertificateId` : Replace with the `id` value obtained when you registered the unmanaged DTS root CA. ```json filename:"Request body" { "active": true } ``` In the 3-tier model, a DTS intermediate CA sits between the DTS root CA and the RICAL Signer Certificate (RSC). The DTS root CA signs the intermediate CA, and the intermediate CA signs the RSC. **Generate a self-signed root certificate (DTS root CA)** Use your preferred cryptographic library or tool to generate a self-signed root certificate (DTS root CA). In the 3-tier model, this certificate will be used to sign the DTS intermediate CA certificate (rather than signing the signer certificate directly). Ensure it meets the requirements specified in [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) and in the [DTS root CA specific requirements](/docs/digital-trust-service/certificates-overview#dts-root-ca-specific-requirements) section. When using unmanaged (external) certificates, the DTS provider assumes full responsibility for the secure management of the uploaded root certificates and all subordinate certificates. This includes ensuring the protection, proper issuance, and timely revocation of certificates under the uploaded root, as MATTR VII does not manage or monitor these certificates on the DTS provider's behalf. **Register the external DTS root CA certificate with MATTR VII** Register the DTS root CA and enable the 3-tier model so it requires an intermediate CA in its chain of trust. 1. Expand the **Platform Management** menu in the navigation panel on the left-hand side. 2. Click on **Certificates**. 3. Select **Create new**. 4. Use the *Type* dropdown to select **DTS CA**. 5. Use the *Management method* dropdown to select **Externally managed**. 6. Paste/upload the PEM-encoded DTS root CA certificate into the **Certificate PEM file** field.\ The certificate must meet the following requirements: * Valid * Not expired * Compliant with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) 7. Under **Certificate chain**, select **Three-tier with Intermediate CAs**. This configures the DTS root CA to sign an intermediate CA rather than signing the signer certificate directly. 8. Select **Create** to register the unmanaged DTS root CA certificate. The newly created unmanaged DTS root CA is created in an inactive state. You can only activate it after you create a DTS intermediate CA and at least one signer associated with it. Make a request of the following structure to [create an unmanaged DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/createDtsCaCertificate). To enable the 3-tier model, set `useIntermediateCa` to `true`: ```http filename:"Request" POST /v1/ecosystems/certificates/ca ``` ```json filename:"Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\n...\r\n-----END CERTIFICATE-----\r\n", "useIntermediateCa": true } ``` * `certificatePem` : This required parameter contains the PEM-encoded DTS root CA certificate. The certificate must meet the following requirements: * Valid * Not expired * Compliant with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) * `useIntermediateCa` : Set this to `true` to require and use intermediate CA certificates as part of this DTS root CA's chain of trust. This field can only be set for unmanaged (external) DTS root CA certificates. Changing this value later requires deleting all subordinate certificates. The response will include an `id` property, which is a unique identifier for the unmanaged DTS root CA. This identifier will be used in subsequent operations to reference this unmanaged DTS root CA. **Generate and sign the DTS intermediate CA certificate** Use your preferred cryptographic library or tool to generate a DTS intermediate CA certificate and sign it with the DTS root CA private key. Ensure it meets the [DTS intermediate CA specific requirements](/docs/digital-trust-service/certificates-overview#dts-intermediate-ca-specific-requirements), including matching the country of the DTS root CA and remaining within the DTS root CA's validity period. Unlike the signer certificate, MATTR VII does not issue a Certificate Signing Request (CSR) for the intermediate CA. You generate the intermediate CA's key pair and sign its certificate entirely within your own PKI, then upload the finished certificate in the next step. **Register the DTS intermediate CA certificate with MATTR VII** Register the signed intermediate CA certificate under the DTS root CA you created in the previous step. 1. Scroll down to the *Child certificates* section. 2. In the **Intermediate CA** section, select **Add new**. 3. Paste/upload the PEM-encoded DTS intermediate CA certificate into the **Certificate PEM file** field. 4. Under **Allowed child certificates**, select the signer types this intermediate CA is allowed to sign: * **VSC (VICAL Signer Certificate)** to sign a VICAL Signer. * **RSC (RICAL Signer Certificate)** to sign a RICAL Signer. Select both if the intermediate CA will sign both signer types. 5. Select **Create** to register the DTS intermediate CA certificate. Make a request of the following structure to [create a DTS intermediate CA certificate](/docs/api-reference/platform/dts-intermediate-ca-certificates/createDtsIntermediateCaCertificate) under the DTS root CA: ```http filename:"Request" POST /v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate ``` * `dtsCaCertificateId` : Replace with the `id` value obtained when you registered the unmanaged DTS root CA in the previous step. ```json filename:"Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\n...\r\n-----END CERTIFICATE-----\r\n", "usages": ["VICAL", "RICAL"] } ``` * `certificatePem` : This required parameter contains the PEM-encoded DTS intermediate CA certificate, signed by the DTS root CA. * `usages` : This required parameter specifies the intended usages for this intermediate CA. It must contain at least one value (`VICAL`, `RICAL`). Include the list type whose signer will chain to this intermediate CA (use `VICAL` for a VICAL Signer, `RICAL` for a RICAL Signer, or both if the intermediate CA will sign both). The response will include an `id` property, which is the unique identifier for the DTS intermediate CA certificate. You will use this identifier when creating the signer. **Create a RICAL Signer** Create a RICAL Signer under the DTS intermediate CA. In the 3-tier model, the RICAL Signer references the intermediate CA rather than the DTS root CA. 1. Navigate to the **Certificates** page and select the DTS root CA, then select the DTS intermediate CA you registered in the previous step. 2. In the **RSC – RICAL Signer Certificate** section, select **Add new**.\ MATTR VII creates a RICAL Signer in a *pending* state and generates a Certificate Signing Request (CSR) for it. Make a request of the following structure to [create a RICAL signer](/docs/api-reference/platform/rical-signers/createRicalSigner) that references the DTS intermediate CA: ```http filename:"Request" POST /v1/ecosystems/certificates/rical-signers ``` ```json filename:"Request body" { "intermediateCaId": "c1bbe671-21f8-5358-9537-eed4669b43f3" } ``` * `intermediateCaId` : Replace with the `id` of the DTS intermediate CA. In the 3-tier model, the RICAL signer references the intermediate CA rather than the DTS root CA. The response includes the RICAL signer `id` and a `csrPem`. **Generate and sign the RICAL Signer Certificate (RSC)** 1. Use the **Download** or **Copy** buttons in the **Step 1. Download the RSC Certificate Signing Request (CSR)** section of the RICAL Signer detail page to obtain the CSR. 2. Using your preferred cryptographic tool, generate and sign the RSC using the CSR from the previous step. In the 3-tier model, the RSC must be signed by the DTS intermediate CA's private key. **Associate the RSC with the RICAL Signer** Upload the signed RSC to the RICAL Signer and activate it. 1. On the RICAL Signer detail page, under **Step 2. Upload signed RSC**, paste/upload the PEM-encoded RSC into the **Certificate PEM file** field. 2. Use the *Status* radio button to set the RICAL Signer to **Active**. 3. Select **Update** to associate the RSC and activate the RICAL Signer. Make a request of the following structure to [update the RICAL signer](/docs/api-reference/platform/rical-signers/updateRicalSigner) to associate the signed RSC and activate it: ```http filename:"Request" PUT /v1/ecosystems/certificates/rical-signers/{ricalSignerId} ``` * `ricalSignerId` : Replace with the RICAL signer `id` from the previous step. ```json filename:"Request body" { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIDXTCCAkWgAwIBAgIJAL5...\r\n-----END CERTIFICATE-----\r\n" } ``` * `active` : Set to `true`. A RICAL signer can only be activated once a valid `certificatePem` is provided. * `certificatePem` : The PEM-encoded RSC generated from the CSR. **Activate the DTS root CA** 1. Navigate back to the **Certificates** page in the MATTR Portal. 2. Select the DTS root CA you created in the first step. 3. Use the *Status* radio button to set the DTS root CA to **Active**. 4. Select **Update** to activate the DTS root CA. Make a request of the following structure to [update the unmanaged DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/updateDtsCaCertificate) and activate it: ```http filename:"Request" PUT /v1/ecosystems/certificates/ca/{dtsCaCertificateId} ``` * `dtsCaCertificateId` : Replace with the `id` value obtained when you registered the unmanaged DTS root CA. ```json filename:"Request body" { "active": true } ``` ### Manually Publish a RICAL [#manually-publish-a-rical] After you have created your participants and set up the signing certificate chain, you can publish a RICAL that includes the trusted Reader Root Certificates. When you publish the RICAL, MATTR VII signs a list that includes the information you provided for each Reader Root Certificate. 1. Navigate to the **Trust lists** page under the *Digital Trust Service* section. 2. Select the **RICAL (Trusted verifiers)** tab. 3. Enter a meaningful *Provider name* to identify the provider of the RICAL. This is included in the RICAL metadata and used by wallets to identify the source of the RICAL. 4. Select the **Create** button. 5. Review the preview area where you can see all Reader Root Certificates included in the RICAL. 6. Select **Generate & Publish** when you are ready.\ The RICAL is now generated and published, and a modal is displayed where you can: * Use the **Download** button to download the RICAL file (CBOR). * Use the **Copy** button to copy a link to the public endpoint where wallets can access the RICAL. First, make a request of the following structure to [update the RICAL configuration](/docs/api-reference/platform/rical-configuration/updateRicalConfiguration) and set the RICAL provider name. A RICAL configuration is required before a RICAL can be created: ```http filename:"Request" PUT /v1/ecosystems/{ecosystemId}/ricals/configuration ``` * `ecosystemId` : Replace with the `id` value of your ecosystem. ```json filename:"Request body" { "ricalProvider": "Example Provider" } ``` * `ricalProvider` : This required parameter is the provider name included in the RICAL metadata and used by wallets to identify the source of the RICAL. Then, make a request of the following structure to [create (generate and publish) a RICAL](/docs/api-reference/platform/rical/createRical) based on your ecosystem policy: ```http filename:"Request" POST /v1/ecosystems/{ecosystemId}/ricals ``` The response includes the `ricalIssueID` and issuance `date` of the published RICAL. Wallets can retrieve it from the public [Retrieve latest RICAL](/docs/api-reference/platform/rical/getLatestRical) endpoint: ```bash curl -o rical-latest.cbor \ https://your-tenant.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals/public/latest ``` ### Configure RICAL auto-generation and publishing (optional) [#configure-rical-auto-generation-and-publishing-optional] You can optionally set up auto-generation of your RICAL so it is generated and published on a schedule. 1. Return to the **Trust lists** page under the *Digital Trust Service* section. 2. Select the **RICAL (Trusted verifiers)** tab. 3. Expand the *RICAL configuration* panel. 4. Use the *Generation method* radio button to select **Auto generate**. 5. Use the *Auto generate frequency* dropdown list to select how often you want the RICAL to be automatically generated and published (daily/weekly). 6. Select the **Update** button. 7. Review the preview area where you can see all Reader Root Certificates included in the RICAL. Note that the RICAL is not generated and published yet. It will only be generated and published automatically based on the frequency you selected in step 5 above. If you want to generate and publish the RICAL immediately, you can select the **Generate & Publish** button. Make a request of the following structure to [update the RICAL configuration](/docs/api-reference/platform/rical-configuration/updateRicalConfiguration) and enable scheduled auto-publishing: ```http filename:"Request" PUT /v1/ecosystems/{ecosystemId}/ricals/configuration ``` ```json filename:"Request body" { "ricalProvider": "Example Provider", "autoPublish": { "enabled": true, "frequency": "Daily" } } ``` * `autoPublish.enabled` : Set to `true` to enable scheduled automatic generation and publishing of the RICAL. * `autoPublish.frequency` : Required when `enabled` is `true`. How often the RICAL is automatically generated and published. One of `Daily` or `Weekly`. When auto-publishing is enabled, the RICAL is generated and published automatically on the schedule you set. You can still generate and publish a RICAL immediately at any time by calling the [Create a RICAL](/docs/api-reference/platform/rical/createRical) endpoint. ### View Previously Published RICALs (optional) [#view-previously-published-ricals-optional] 1. Return to the **Trust lists** page under the *Digital Trust Service* section. 2. Select the **RICAL (Trusted verifiers)** tab. 3. Scroll down and select the **View Previously Published** button to open the *Previously generated RICAL* view, which lists each RICAL with its *Issue ID*, *Generated at* time, *File name*, *Trigger method*, and *Status*. 4. Use the **Download** button to download any previously published RICAL. Make a request of the following structure to [retrieve all RICALs](/docs/api-reference/platform/rical/getRicals) published in your ecosystem. This endpoint is public and does not require authentication: ```http filename:"Request" GET /v1/ecosystems/{ecosystemId}/ricals/public ``` The response is a JSON list of the published RICALs with their `ricalIssueID`, issuance `date`, and `filename`. To download a specific RICAL file (a CBOR-encoded file), call the [Retrieve specific RICAL](/docs/api-reference/platform/rical/getRical) endpoint with the relevant `ricalIssueId`: ```bash curl -o rical.cbor \ https://your-tenant.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals/public/{ricalIssueId} ``` ## Next steps [#next-steps] Now that you have published your RICAL, you can share the public endpoint with wallet providers so they can consume the RICAL and establish trust in the verifiers included in it. Refer to the [RICAL consumption guide](/docs/digital-trust-service/rical-consumption) to learn how wallets can retrieve, validate and use a RICAL. # Overview URL: /docs/digital-trust-service/rical-overview ## Introduction [#introduction] A RICAL (Reader Identity Certificate Authority List) is a mechanism defined in [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) Annex F to support establishing trust in digital ecosystems where holders need to verify the identity of relying parties (verifiers) requesting data from their mDocs. ISO/IEC 18013-5 uses the term **reader** for the entity that requests data from an mDoc. Throughout this documentation we use **verifier** or **relying party** for the same role. Certificate names defined by the standard (for example, *Reader Root Certificate* and *Reader Authority Certificate*) keep their ISO terminology. It is the counterpart to a [VICAL](/docs/digital-trust-service/vical-overview): where a VICAL lets relying parties consume issuer trust information from a single authoritative source, a RICAL lets holder applications (wallets) consume verifier trust information in the same centralized way. For example, consider the case of a holder presenting a Mobile Driver's License (mDL) to a verifier. Different relying parties (law enforcement agencies, age verification services, financial institutions) each operate their own Reader Authority Certificates. Without a RICAL, every wallet would have to individually assess and trust each verifier's root certificate, which becomes impractical as the number of verifiers in an ecosystem grows. A RICAL solves this by collecting and validating Reader Root Certificates from different relying parties, and then cryptographically signing them into a single list. When a wallet trusts a RICAL, it can trust any verifier whose certificate chain anchors to a root included in the RICAL, without maintaining a direct trust relationship with each individual verifier. ## RICAL roles [#rical-roles] * **RICAL Provider**: Operates the RICAL and provides it as a service to ecosystem participants. The RICAL provider collects and validates Reader Root Certificates from relying parties, compiles them into a standardized RICAL format and distributes the result to holders. * **Relying parties (verifiers)**: Request data from mDocs and present their verifier certificate so holders can authenticate them. * **Holders (Wallets)**: Consume the RICAL and use verifier information to verify the identity of relying parties before releasing data. ## RICAL components [#rical-components] * **RICAL metadata:** General information about the RICAL itself: * Version. * Provider. * Issuance date. * Unique identifier. * **RICAL records:** Each record carries the information a wallet needs to establish trust in a verifier's certificate chain: * The DER-encoded trusted Reader Root Certificate. * The certificate serial number. * The Subject Key Identifier (SKI). * The issuing country and (where applicable) state or province name. * The DER-encoded issuer and subject distinguished names. * The certificate validity period (`notBefore` and `notAfter`). Unlike a VICAL, a RICAL record does **not** include a `docType` array. A trusted Reader Root Certificate in a RICAL is treated as authoritative for the ecosystem rather than scoped to specific credential types. Authorization of which data a verifier may request is conveyed separately through the verifier's signed request and the holder's consent flow, not through the trust list itself. ## RICAL structure [#rical-structure] The decoded RICAL payload is structurally similar to a VICAL, but each record describes a trusted Reader Root Certificate rather than an issuer, and there is no per-record `docType` array: ```text RICAL = { "version" : tstr, ; RICAL structure version, currently "1.0" "provider" : tstr, ; Identifies the RICAL provider "date" : tdate, ; date-time of RICAL issuance "id" : uint, ; Uniquely identifies this specific issue of the RICAL "type" : tstr, ; RICAL type, currently "reader" "certificateInfos" : [+ { "certificate" : bstr, ; DER-encoded X.509 certificate "serialNumber" : biguint, ; Serial number of the certificate "ski" : bstr, ; Subject Key Identifier "issuingCountry" : tstr, ; ISO 3166-1 alpha-2 country code "stateOrProvinceName" : tstr, ; State or province (sub-national issuers) "issuer" : bstr, ; DER-encoded issuer distinguished name "subject" : bstr, ; DER-encoded subject distinguished name "notBefore" : tdate, ; Certificate validity start "notAfter" : tdate ; Certificate validity end }] } ``` ## How it works [#how-it-works] 1. The RICAL provider establishes its own root certificate with an associated Public Key Infrastructure (PKI) chain of certificates, based on the [chain of trust](/docs/concepts/chain-of-trust) model. 2. The RICAL provider collects and validates Reader Root Certificates from different relying parties. Each of these roots is vetted by the RICAL provider before inclusion. 3. The RICAL provider uses their chain of trust end-entity certificate to sign the validated Reader Root Certificates into a single list. 4. Each relying party uses their own Reader Authority Certificate (and the associated PKI chain) to sign verifier requests. 5. Holders can consume the RICAL in one of two ways: * Download the RICAL directly from the provider's website. * Retrieve the RICAL via an endpoint exposed by the provider as an API. 6. When a holder receives a request from a verifier, the wallet validates the verifier's signature and referenced PKI certificate chain against the RICAL to ensure that the chain anchors to a trusted Reader Root Certificate present in the list. 7. Upon successful validation, the wallet can confidently authenticate the relying party without maintaining an individual trust relationship with each verifier. ## Linked certificates [#linked-certificates] A **linked certificate** lets a participant rotate the Reader Root Certificate they use in the ecosystem without breaking trust for holder applications (wallets) that already trust the previous certificate. When you add a new certificate for a participant, you can add it as a **successor** to a previously uploaded certificate by selecting the **predecessor** and uploading a **link certificate** that ties the new certificate to the previous one. This preserves trust continuity for the participant across the rotation, so wallets can continue to authenticate the verifier without first having to consume an updated RICAL. To add a linked certificate, refer to the [RICAL guide](/docs/digital-trust-service/rical-guide). ## Next steps [#next-steps] * Follow the [RICAL guide](/docs/digital-trust-service/rical-guide) to set up a RICAL and publish a list of trusted Reader Root Certificates, using either the Portal or the MATTR VII API. * Refer to the [RICAL consumption guide](/docs/digital-trust-service/rical-consumption) to learn how wallets can retrieve, validate and use a published RICAL. # Trusted Lists URL: /docs/digital-trust-service/trusted-lists Description: The categories of trust a Digital Trust Service publishes (trusted issuers, trusted readers, and trusted wallets) and why each one matters. A Digital Trust Service (DTS) makes trust decisions scalable by publishing **trusted lists**. Instead of every participant establishing a direct relationship with every other participant, each participant trusts the lists published by the network operator. Those lists answer three separate questions. * **Who is authorized to issue** a given credential type? * **Who is authorized to read** (request) a given credential type? * **Which wallets and technologies** are certified to participate? Understanding these three categories first makes it easier to see where the specific mechanisms [VICAL](/docs/digital-trust-service/vical-overview) and [RICAL](/docs/digital-trust-service/rical-overview) fit in. See [DTS options](/docs/digital-trust-service/consuming-trust-overview) for how MATTR lets you publish and consume each category. ## Trusted Issuer Lists [#trusted-issuer-lists] A Trusted Issuer List defines which issuers are authorized to issue specific types of verifiable credentials. A verifier or wallet can then trust a credential without needing a direct relationship with each issuer. It only needs to trust the list. **Why it matters:** Maintaining an up-to-date relationship with every issuer across multiple jurisdictions and credential types is operationally complex. Certificates rotate, new issuers come online, and different authorities publish independently. A Trusted Issuer List delegates that work to the network operator. **Example:** A retailer verifying mobile driver's licenses (mDLs) needs to accept licenses from every participating state. Rather than tracking each state's Issuing Authority Certificate Authority (IACA) individually, the retailer's verifier trusts a single issuer list that the network operator keeps current as new states come online. **How MATTR implements it:** through ecosystem policies exposed via the [MATTR VII APIs](/docs/api-reference/platform/policy/getLatestEcosystemPolicy) and through [VICAL](/docs/digital-trust-service/vical-overview), the ISO/IEC 18013-5 standardized mechanism for publishing issuer certificate authority lists. See [DTS options](/docs/digital-trust-service/consuming-trust-overview) for how to choose between them. ## Trusted Reader Lists [#trusted-reader-lists] A Trusted Reader List defines which verifiers (readers) are authorized to request specific types of credentials. A holder or wallet can then trust a verifier before presenting data to it. **Why it matters:** Holders should only disclose data to parties that are entitled to request it. A Trusted Reader List gives wallets a way to recognize accredited verifiers and warn users about, or block, requests from parties that are not on the list. **Example:** A citizen's wallet receives a request for their date of birth. Before the wallet shows a consent screen, it checks that the requesting verifier appears on the network's reader list. If the verifier is recognized, the wallet can display a trust indicator. If it is not, the wallet can warn the user or decline the request. **How MATTR implements it:** through [RICAL](/docs/digital-trust-service/rical-overview) (Reader Certificate Authority List), the mechanism for publishing reader trust lists. Ecosystem policies exposed via the [MATTR VII APIs](/docs/api-reference/platform/policy/getLatestEcosystemPolicy) can also express which participants are permitted to verify a given credential type. ## Trusted Wallets and Trusted Technologies [#trusted-wallets-and-trusted-technologies] A Trusted Technologies list defines which certified applications and solutions may participate in the network. This can include digital wallets, identity agents, registers, and verifiable credentials that meet established criteria for secure and reliable operation. **Why it matters:** The integrity of a trust network depends on the software participants use. Trusting only certified wallets and technologies keeps uncertified, and potentially malicious, applications out of sensitive interactions. **Example:** An issuer wants to ensure its credentials are only claimed by wallet applications it has authorized. It requires wallets to cryptographically prove their authenticity before claiming, so only recognized wallet instances can receive its credentials. **How MATTR implements it:** through [wallet attestation](/docs/issuance/credential-issuance/wallet-attestation), which validates a wallet instance through a certificate-based trust chain before it can claim credentials, and through the recognized standards and certification criteria defined by the network's [trust framework](/docs/digital-trust-service#trust-framework). ## Next steps [#next-steps] * [DTS options](/docs/digital-trust-service/consuming-trust-overview): the options MATTR offers for publishing and consuming each category of trust. * [VICAL overview](/docs/digital-trust-service/vical-overview): the standardized mechanism for issuer trust lists. * [RICAL overview](/docs/digital-trust-service/rical-overview): the mechanism for reader trust lists. # How to consume a VICAL as a relying party URL: /docs/digital-trust-service/vical-consumption Once a VICAL provider has published a VICAL, relying parties need to retrieve it, verify its authenticity, and load the trusted IACAs into their verification solution. The steps below describe how to do that against a VICAL published by a MATTR-hosted ecosystem. ### Retrieve the VICAL provider's root CA certificate [#retrieve-the-vical-providers-root-ca-certificate] Call the [Retrieve all public DTS root CA certificates](/docs/api-reference/platform/dts-root-ca-certificates/getDtsCaCertificatePublic) endpoint to obtain the root certificate(s) used by the VICAL provider as the anchor of its signing chain. The endpoint is public and does not require authentication. ```bash curl https://your-tenant.vii.au01.mattr.global/v1/ecosystems/public/certificates/ca ``` This endpoint is public and does not require authentication. It should be provided by the VICAL provider to all relying parties in the ecosystem. The response includes one or more PEM-encoded root certificates: ```json { "rootCertificates": [ { "certificate": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "notBefore": "2025-10-22T00:00:00Z", "notAfter": "2030-10-22T00:00:00Z", "fingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "commonName": "Example VICAL Provider Root CA" } ] } ``` Persist these certificates as your trust anchor for the VICAL provider. They only need to be refreshed when the provider rotates its root. ### Retrieve the latest VICAL [#retrieve-the-latest-vical] Call the [Retrieve latest VICAL](/docs/digital-trust-service/vical-api-reference/general#retrieve-latest-vical) endpoint to download the current VICAL. ```bash curl -o vical-latest.cbor \ https://your-tenant.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public/latest ``` This endpoint is public and does not require authentication. It should be provided by the VICAL provider to all relying parties in the ecosystem. The response is a binary CBOR file (`application/cbor`) encoded as a [COSE\_Sign1](https://datatracker.ietf.org/doc/html/rfc9052) structure, as defined in [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) Annex C. Relying parties should poll this endpoint on a schedule that matches the VICAL provider's publishing cadence so they always operate against the latest set of trusted issuers. ### Validate the VICAL signature and certificate chain [#validate-the-vical-signature-and-certificate-chain] The VICAL is signed by a VICAL Signer certificate, which itself chains back to the VICAL provider's root CA retrieved in step 1. Before trusting any data in the VICAL, perform the following checks: 1. Extract the VICAL Signer certificate from the COSE\_Sign1 unprotected header (`x5chain`, COSE label `33`). 2. Verify the COSE\_Sign1 signature using the public key from that signer certificate and the signature algorithm declared in the protected header (for example, ES256 / COSE algorithm `-7`). 3. Build the certificate chain from the signer certificate up to the root CA from step 1, and validate each link: signature, validity period, key usage, and revocation status (CRL). 4. Confirm the root of the chain matches one of the trust-anchor certificates you persisted in step 1. If any of these checks fail, reject the VICAL and continue using the previously trusted version. ### Decode the VICAL payload [#decode-the-vical-payload] Once the signature has been verified, decode the COSE\_Sign1 payload to access the VICAL data. The decoded structure looks like this (values are illustrative): ```json { "protectedHeader": { "1": -7 }, "unprotectedHeader": { "33": { "type": "X.509 Certificate", "subject": "C=AU, CN=example-dts.vii.au01.mattr.global VICAL Signer, O=Example Provider", "issuer": "C=AU, CN=Example Provider Root CA, O=Example Provider", "validFrom": "2025-11-11T02:26:13.000Z", "validTo": "2029-02-10T02:26:13.000Z", "fingerprint": "68:58:45:41:5A:AF:10:3A:85:01:78:E2:40:C9:59:51:AB:9B:10:13", "pem": "-----BEGIN CERTIFICATE-----\nMIICnzCCAkagAwIBAgIK...\n-----END CERTIFICATE-----" } }, "payload": { "version": "1.0", "vicalProvider": "Example Provider", "date": "2025-11-15T20:59:01.000Z", "vicalIssueID": 41, "certificateInfos": [ { "certificate": { "type": "X.509 Certificate", "subject": "CN=Example IACA, O=Example DLD, ST=US-XX, C=US", "issuer": "CN=Example IACA, O=Example DLD, ST=US-XX, C=US", "validFrom": "2024-01-01T00:00:00.000Z", "validTo": "2034-01-01T00:00:00.000Z", "pem": "-----BEGIN CERTIFICATE-----\nMIICeDCCAh+gAwIBAgIQ...\n-----END CERTIFICATE-----", "isCertificateAuthority": true }, "serialNumber": "5A5BB04A119A35796D2AD477D93A1ACC", "ski": "3C:4C:6C:DF:E7:82:34:2E:E1:14:E6:CE:AD:12:0A:39:FD:08:34:6B", "issuingCountry": "US", "stateOrProvinceName": "US-XX", "notBefore": "2024-01-01T00:00:00.000Z", "notAfter": "2034-01-01T00:00:00.000Z", "docType": ["org.iso.18013.5.1.mDL"], "issuingAuthority": "CN=Example IACA, O=Example DLD, ST=US-XX, C=US" } ] } } ``` The `payload.certificateInfos` array contains one entry per trusted IACA. Each entry includes the PEM-encoded IACA, its validity period, the issuing authority's identifiers, and the array of `docType` values it is authorized to sign. ### Load the trusted IACAs into your verification solution [#load-the-trusted-iacas-into-your-verification-solution] Iterate over `payload.certificateInfos` and extract each `certificate`. In raw CBOR, `certificate` is the DER-encoded X.509 IACA byte string (the JSON above is a rendered view). These IACAs are the trust anchors you use when validating presented mDocs: ```javascript import { decode } from "cbor-x"; const vicalBytes = await fetch( "https://your-tenant.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public/latest" ).then((r) => r.arrayBuffer()); // COSE_Sign1 structure: [protected, unprotected, payload, signature] const coseSign1 = decode(new Uint8Array(vicalBytes)); const payload = decode(coseSign1[2]); const trustAnchors = payload.certificateInfos.map((info) => ({ certificateDer: info.certificate, issuingAuthority: info.issuingAuthority, docTypes: info.docType, validFrom: info.notBefore, validTo: info.notAfter, })); ``` When verifying a presented mDoc: 1. Look up the IACA referenced by the mDoc's Document Signer Certificate against your loaded trust anchors. 2. Confirm the mDoc's `docType` is listed under that IACA's authorized `docType` array. If it is not, reject the credential even when the signature chain is otherwise valid. 3. Validate the full PKI chain from the mDoc's signer up to the IACA as the trust anchor. This mechanism lets you verify mDocs from any issuer represented in the VICAL without maintaining individual trust relationships with each issuing authority. # Learn how to set up a VICAL URL: /docs/digital-trust-service/vical-guide ## Introduction [#introduction] The purpose of a [Verified Issuer Certificate Authority List (VICAL)](/docs/digital-trust-service/vical-overview) is to enable different participants in a digital ecosystem to rely on a single trusted framework. This guide will walk you through setting up a VICAL and publishing a policy that defines trusted participants and credential types. Each step can be completed either through the [Portal](/docs/platform-management/portal) or via the MATTR VII API. Select the tab that matches how you want to work. ## Prerequisites [#prerequisites] * Make sure you understand the concepts of a [VICAL](/docs/digital-trust-service/vical-overview) and how it relates to a [Digital Trust Service (DTS)](/docs/digital-trust-service). * You need access to an existing MATTR VII tenant with either the `DTS Provider` or `Admin` role. Refer to the [Getting started with the Portal](/docs/platform-management/portal#getting-started) tutorial to learn how to create a tenant and assign roles. * To use the API, you also need to [obtain a bearer access token](/docs/api-reference#obtain-an-access-token) to include in the `Authorization` header of each request. ## Guide overview [#guide-overview] Publishing a VICAL comprises the following steps: 1. [Create an Ecosystem](#create-an-ecosystem): This is the overarching entity that holds participants and credential types together. Only required if you don't already have an ecosystem on your tenant. 2. [Create participants and add issuer certificates](#create-participants-and-add-issuer-certificates): These are the issuers that will be part of the VICAL. Each participant includes one or more IACA certificates and the credential types they are allowed to issue. 3. [Set up the VICAL signing certificate chain](#set-up-the-vical-signing-certificate-chain): Establish the DTS root CA and VICAL Signer Certificate used to sign the VICAL, using either a 2-tier or 3-tier model. 4. [Manually publish a VICAL](#manually-publish-a-vical): Configure the VICAL provider, then generate and publish a VICAL that includes your participants and their credential types. 5. [Configure VICAL auto-generation and publishing](#configure-vical-auto-generation-and-publishing-optional) (optional): Set the VICAL to automatically generate and publish on a daily or weekly schedule. 6. [View previously published VICALs](#view-previously-published-vicals-optional) (optional): Review the history of previously published VICALs and download their policy files. ### Create an Ecosystem [#create-an-ecosystem] Creating an Ecosystem is a one-time step per tenant. It is only required if you don't already have an ecosystem on your tenant. If you already have an ecosystem (for example, because you created one when setting up a [RICAL](/docs/digital-trust-service/rical-guide)), you will not see the option to create a new one (in the Portal) and should skip directly to the next step. A single ecosystem is shared across both your VICAL and RICAL. Perform the following steps to create an Ecosystem: 1. Log in to the [MATTR Portal](https://portal.mattr.global/). 2. Navigate to the **Ecosystem** page under the *Digital Trust Service* section. 3. Enter a name for your Ecosystem, such as "My Digital Trust Service". 4. Select the **Create** button. Make a request of the following structure to [create an ecosystem](/docs/api-reference/platform/ecosystems/createEcosystem): ```http filename:"Request" POST /v1/ecosystems ``` ```json filename:"Request body" { "name": "My Digital Trust Service" } ``` * `name` : This required parameter is the name used to identify your ecosystem. The response will include an `id` property, which is the unique identifier for the ecosystem. You will use this `ecosystemId` in subsequent requests to reference this ecosystem. ### Create participants and add issuer certificates [#create-participants-and-add-issuer-certificates] Participants are entities that represent issuers that will be included in the VICAL. For each participant, you will need to provide one or more IACA certificates that will be used as the trust anchor when signing mDocs, and define what credential types they are allowed to issue. Perform the following steps to create a participant: 1. Select the **Participants** page under the *Digital Trust Service* section (this page is only visible if you have an existing ecosystem. If you don't have an ecosystem, you will need to return to step 1 above and create it first). 2. Select the **Create new** button.\ The *Create participant* form appears, starting from Step 1 (*Details*). 3. Insert a meaningful *Name* for the participant (e.g. "Montcliff DMV"). 4. Use the *Country* dropdown list to select the Participant’s country (optional). Note that when selected, this value must match the *Country* value in the IACA certificate associated with this participant. 5. If you select a country, a *State or Province* dropdown list is displayed. You can use it to select the Participant’s state or province (optional). Note that when selected, this value must match the *StateOrProvinceName* value in the IACA certificate associated with this participant. 6. Insert the participant’s *Address* (optional). 7. Insert the participant’s *Phone number* (optional). 8. Use the *Status* radio button to set the participant as **Active**. 9. Click the **Next** button.\ You are directed to Step 2 (*Certificates*). 10. Click the **Create** button to create the participant. 11. Select the **Issuer certificates** tab. 12. Select the **Add new** button to add an issuer certificate for the participant.\ The *Add issuer certificate* form appears. 13. Upload the PEM file you want to use as this participant’s identifier for issuing mDocs (this must be a valid IACA certificate and match any values set for *Country* and *State or Province* above).\ You should now see the certificate summary and details. 14. Use the *Credential types valid for* field to select the credential types that this participant will be allowed to issue. * You can insert as many credential types as you want. * You can use the pre-populated options (`org.iso.18013.5.1.mDL` and/or `org.iso.23220.photoid.1`) or insert custom credential types that are relevant to your ecosystem. * The credential type is just a string value and does not need to match any values in the certificate. It is only used to link the participant to the credential types they are allowed to issue. 15. Scroll down and use the *Status* dropdown list to set the certificate as ***Active***. 16. *(Optional)* To maintain trust continuity when rotating a participant's IACA certificate, expand the **Link Certificate** section and add this certificate as a successor to a previously uploaded certificate (refer to [Linked certificates](/docs/digital-trust-service/vical-overview#linked-certificates)): * Use the *Predecessor certificate* dropdown to select the previous certificate that this new one succeeds. The dropdown lists the participant's existing certificates because the predecessor must already exist on your tenant before you can link to it. * Upload the link certificate into the *Link Certificate PEM file* field. The link certificate is the participant's proof that they own both the predecessor and the new certificate, which is what lets you accept the new one while preserving trust in the previous one. This option is only available once at least one certificate has been added for the participant. 17. Click the **Add** button. Repeat the above steps for each participant you want to include in your VICAL. Make a request of the following structure to [create a participant](/docs/api-reference/platform/participants/createEcosystemParticipant) in your ecosystem. The participant's IACA certificate and its authorized credential types are provided as a `mobile` identifier: ```http filename:"Request" POST /v1/ecosystems/{ecosystemId}/participants ``` * `ecosystemId` : Replace with the `id` value obtained when you created the ecosystem. ```json filename:"Request body" { "name": "Montcliff DMV", "status": "Active", "isIssuer": true, "country": "US", "stateOrProvince": "US-XX", "identifiers": { "mobile": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\n...\r\n-----END CERTIFICATE-----\r\n", "status": "Active", "docTypes": ["org.iso.18013.5.1.mDL"] } ] } } ``` * `name` : This required parameter is a meaningful name used to identify the participant. * `status` : Set this to `Active` so the participant is included in the ecosystem policy and the VICAL. Only active participants are published. * `isIssuer` : Set this to `true` so the participant can act as an issuer in the ecosystem. * `country` : This *optional* parameter is the participant's [Alpha 2 country code](https://www.iso.org/glossary-for-iso-3166.html). When provided, it must match the `country` value in the IACA certificate. * `stateOrProvince` : This *optional* parameter is the participant's [ISO 3166-2](https://www.iso.org/standard/72483.html) state or province. When provided, it must match the `stateOrProvinceName` value in the IACA certificate. * `identifiers.mobile` : An array containing the participant's mDoc identifier(s). Each entry includes: * `certificatePem` : The PEM-encoded IACA certificate this participant uses as the trust anchor when signing mDocs. It must be a valid IACA as defined in Annex B of [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * `status` : Set to `Active` to include this certificate in the VICAL. * `docTypes` : The credential types this participant is allowed to issue with this IACA. The value is a string and does not need to match any value in the certificate. The response will include an `id` property identifying the participant. Repeat this request for each participant you want to include in your VICAL. ### Set up the VICAL signing certificate chain [#set-up-the-vical-signing-certificate-chain] Each VICAL must be signed by a VICAL Signer Certificate (VSC) that chains back to a DTS root CA via a [chain of trust](/docs/digital-trust-service/certificates-overview). This chain is what consuming relying parties use as the trust anchor to validate the authenticity and integrity of the VICAL. If you already have an existing active DTS root CA that you want to use as the trust anchor for your VICAL, you can skip this step. MATTR VII supports both **managed** and **unmanaged (external)** DTS certificates. Select the option that matches how you want to manage your certificate infrastructure. With managed DTS certificates, MATTR VII provisions and maintains the DTS root CA and the signer certificates for you. You create and activate the DTS root CA, and MATTR VII automatically creates a signer (and its certificate) and uses it to sign trust lists as required. Managed DTS certificates always use the [2-tier model](/docs/digital-trust-service/certificates-overview#certificate-models-2-tier-and-3-tier). 1. Navigate to the **Certificates** page under the *Platform Management* section. 2. Select the **Create new** button.\ The *New certificate* form appears. 3. Use the *Type* dropdown list to select **DTS CA**. 4. Use the *Management method* radio button to select **MATTR managed**. 5. Enter a meaningful name in the *Organization* field to identify the organization operating the DTS. 6. Use the *Country* dropdown list to select the country where the organization is located. 7. Select the **Create** button.\ The DTS root CA is created in an inactive state. 8. Scroll down and use the *Status* radio button to select **Active**. 9. Select the **Update** button to activate the DTS root CA. Make a request of the following structure to [create a managed DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/createDtsCaCertificate): ```http filename:"Request" POST /v1/ecosystems/certificates/ca ``` ```json filename:"Request body" { "organisationName": "Example Inc.", "commonName": "Example DTS CA", "country": "US" } ``` * `organisationName` : This required parameter indicates the organization associated with the DTS root CA certificate. * `commonName` : This *optional* parameter indicates the common name of the DTS root CA certificate. If not provided and a [custom domain](/docs/platform-management/custom-domain-overview) is configured and verified, the custom domain is used followed by the words `DTS CA`. If no custom domain is configured, the tenant subdomain is used instead. * `country` : This *optional* parameter indicates the DTS provider's country. If not provided, a country is selected based on the region of the tenant subdomain cloud host. When specified, the value must be a valid [Alpha 2 country code](https://www.iso.org/glossary-for-iso-3166.html) as per [ISO 3166-1](https://www.iso.org/standard/72482.html). The response will include an `id` property identifying the managed DTS root CA. Make a request of the following structure to [update the managed DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/updateDtsCaCertificate) and activate it: ```http filename:"Request" PUT /v1/ecosystems/certificates/ca/{dtsCaCertificateId} ``` * `dtsCaCertificateId` : Replace with the `id` value obtained when you created the managed DTS root CA. ```json filename:"Request body" { "active": true } ``` Once a managed DTS root CA is activated, MATTR VII automatically creates and uses a signer to sign trust lists as required. With unmanaged (external) DTS certificates, you supply and maintain the full certificate chain. You generate the DTS root CA, issue and sign the VICAL Signer Certificates (VSCs), upload the root and each VSC to MATTR VII, and handle renewal and revocation. Select the tab for the certificate model you want to configure. For guidance on choosing a model, see [certificate models: 2-tier and 3-tier](/docs/digital-trust-service/certificates-overview#certificate-models-2-tier-and-3-tier). In the 2-tier model, the DTS root CA certificate directly signs the VICAL Signer Certificate (VSC). **Generate a self-signed root certificate (DTS root CA)** Use your preferred cryptographic library or tool to generate a self-signed root certificate (DTS root CA). In the 2-tier model, this certificate directly signs the signer certificate. Ensure it meets the requirements specified in [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) and in the [certificate requirements](/docs/digital-trust-service/certificates-overview#certificate-requirements) section. When using unmanaged (external) certificates, the DTS provider assumes full responsibility for the secure management of the uploaded root certificates and all subordinate certificates. This includes ensuring the protection, proper issuance, and timely revocation of certificates under the uploaded root, as MATTR VII does not manage or monitor these certificates on the DTS provider's behalf. **Register the external DTS root CA certificate with MATTR VII** 1. Expand the **Platform Management** menu in the navigation panel on the left-hand side. 2. Click on **Certificates**. 3. Select **Create new**. 4. Use the *Type* dropdown to select **DTS CA**. 5. Use the *Management method* dropdown to select **Externally managed**. 6. Paste/upload the PEM-encoded DTS root CA certificate into the **Certificate PEM file** field.\ The certificate must meet the following requirements: * Valid * Not expired * Compliant with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) 7. Select **Create** to register the unmanaged DTS root CA certificate. The newly created unmanaged DTS root CA is created in an inactive state. You can only activate it after you create at least one signer associated with this DTS root CA. Make a request of the following structure to [create an unmanaged DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/createDtsCaCertificate): ```http filename:"Request" POST /v1/ecosystems/certificates/ca ``` ```json filename:"Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\n...\r\n-----END CERTIFICATE-----\r\n" } ``` * `certificatePem` : This required parameter contains the PEM-encoded DTS root CA certificate. The certificate must meet the following requirements: * Valid * Not expired * Compliant with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) The response will include an `id` property, which is a unique identifier for the unmanaged DTS root CA. This identifier will be used in subsequent operations to reference this unmanaged DTS root CA. **Create a VICAL Signer** Create a VICAL Signer under the DTS root CA. In the 2-tier model, the VICAL Signer references the DTS root CA directly. 1. On the detail page of the DTS root CA you just registered, scroll down to the **VSC – VICAL Signer Certificate** section and select **Add new**. 2. Select the **Create** button.\ MATTR VII creates a VICAL Signer in a *pending* state and generates a Certificate Signing Request (CSR) for it. Make a request of the following structure to [create a VICAL Signer](/docs/digital-trust-service/vical-api-reference/signers#create-a-vical-signer) that references the unmanaged DTS root CA: ```http filename:"Request" POST /v1/ecosystems/certificates/vical-signers ``` ```json filename:"Request body" { "caId": "080c670a-2e90-4023-b79f-b706e55e9bc6" } ``` * `caId` : Replace with the `id` value obtained when you created the unmanaged DTS root CA in the previous step. Attempts to provide a managed DTS root CA identifier for manual VICAL Signer creation will result in an error. The response will include two properties which you will use later in this guide: * `id` : The unique identifier for the VICAL Signer. This identifier will be used in subsequent operations to reference this VICAL Signer. * `csrPem` : The X.509 Certificate Signing Request (CSR) in PEM format. You will use this CSR to generate a valid VICAL Signer Certificate (VSC) in the next step. **Generate and sign the VICAL Signer Certificate (VSC)** 1. Use the **Download** or **Copy** buttons in the **Step 1. Download the VSC Certificate Signing Request (CSR)** section of the VICAL Signer detail page to obtain the CSR. 2. Using your preferred cryptographic library or tool, generate and sign a VICAL Signer Certificate (VSC) using the CSR from the previous step. In the 2-tier model, the VSC must be signed by the DTS root CA's private key. Refer to the [VSC specific requirements](/docs/digital-trust-service/certificates-overview#signer-certificate-specific-requirements-vsc-and-rsc) section for details on how to structure a valid VSC. **Associate the VSC with the VICAL Signer** Upload the signed VSC to the VICAL Signer and activate it. 1. On the VICAL Signer detail page, under **Step 2. Upload signed VSC**, paste/upload the PEM-encoded VSC into the **Certificate PEM file** field. 2. Use the *Status* radio button to set the VICAL Signer to **Active**. 3. Select **Update** to associate the VSC and activate the VICAL Signer. Make a request of the following structure to [update the VICAL Signer](/docs/digital-trust-service/vical-api-reference/signers#update-a-vical-signer) to activate and associate it with the generated VSC: ```http filename:"Request" PUT /v1/ecosystems/certificates/vical-signers/{vicalSignerId} ``` * `vicalSignerId` : Replace with the `id` value obtained when you created the VICAL Signer in the previous step. ```json filename:"Request body" { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICbzCCAhSgAwIBAgIKfS7sskyJEh+DOzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\n...\r\n-----END CERTIFICATE-----\r\n" } ``` * `active` : This required boolean indicates whether the VICAL Signer is active or not. Can only be set to `true` when a `certificatePem` is provided. Only active VICAL Signers can be used to sign VICALs. * `certificatePem` : This required parameter contains the PEM-encoded VSC created in the previous step. **Activate the DTS root CA** 1. Navigate back to the **Certificates** page in the MATTR Portal. 2. Select the DTS root CA you created in the first step. 3. Use the *Status* radio button to set the DTS root CA to **Active**. 4. Select **Update** to activate the DTS root CA. Make a request of the following structure to [update the unmanaged DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/updateDtsCaCertificate) and activate it: ```http filename:"Request" PUT /v1/ecosystems/certificates/ca/{dtsCaCertificateId} ``` * `dtsCaCertificateId` : Replace with the `id` value obtained when you registered the unmanaged DTS root CA. ```json filename:"Request body" { "active": true } ``` In the 3-tier model, a DTS intermediate CA certificate sits between the DTS root CA and the VICAL Signer Certificate (VSC). The DTS root CA signs the intermediate CA, and the intermediate CA signs the VSC. **Generate a self-signed root certificate (DTS root CA)** Use your preferred cryptographic library or tool to generate a self-signed root certificate (DTS root CA). In the 3-tier model, this certificate will be used to sign the DTS intermediate CA certificate (rather than signing the signer certificate directly). Ensure it meets the requirements specified in [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) and in the [DTS root CA specific requirements](/docs/digital-trust-service/certificates-overview#dts-root-ca-specific-requirements) section. When using unmanaged (external) certificates, the DTS provider assumes full responsibility for the secure management of the uploaded root certificates and all subordinate certificates. This includes ensuring the protection, proper issuance, and timely revocation of certificates under the uploaded root, as MATTR VII does not manage or monitor these certificates on the DTS provider's behalf. **Register the external DTS root CA certificate with MATTR VII** Register the DTS root CA and enable the 3-tier model so it requires an intermediate CA in its chain of trust. 1. Expand the **Platform Management** menu in the navigation panel on the left-hand side. 2. Click on **Certificates**. 3. Select **Create new**. 4. Use the *Type* dropdown to select **DTS CA**. 5. Use the *Management method* dropdown to select **Externally managed**. 6. Paste/upload the PEM-encoded DTS root CA certificate into the **Certificate PEM file** field.\ The certificate must meet the following requirements: * Valid * Not expired * Compliant with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) 7. Under **Certificate chain**, select **Three-tier with Intermediate CAs**. This configures the DTS root CA to sign an intermediate CA rather than signing the signer certificate directly. 8. Select **Create** to register the unmanaged DTS root CA certificate. The newly created unmanaged DTS root CA is created in an inactive state. You can only activate it after you create a DTS intermediate CA and at least one signer associated with it. Make a request of the following structure to [create an unmanaged DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/createDtsCaCertificate). To enable the 3-tier model, set `useIntermediateCa` to `true`: ```http filename:"Request" POST /v1/ecosystems/certificates/ca ``` ```json filename:"Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\n...\r\n-----END CERTIFICATE-----\r\n", "useIntermediateCa": true } ``` * `certificatePem` : This required parameter contains the PEM-encoded DTS root CA certificate. The certificate must meet the following requirements: * Valid * Not expired * Compliant with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) * `useIntermediateCa` : Set this to `true` to require and use intermediate CA certificates as part of this DTS root CA's chain of trust. This field can only be set for unmanaged (external) DTS root CA certificates. Changing this value later requires deleting all subordinate certificates. The response will include an `id` property, which is a unique identifier for the unmanaged DTS root CA. This identifier will be used in subsequent operations to reference this unmanaged DTS root CA. **Generate and sign the DTS intermediate CA certificate** Use your preferred cryptographic library or tool to generate a DTS intermediate CA certificate and sign it with the DTS root CA private key. Ensure it meets the [DTS intermediate CA specific requirements](/docs/digital-trust-service/certificates-overview#dts-intermediate-ca-specific-requirements), including matching the country of the DTS root CA and remaining within the DTS root CA's validity period. Unlike the signer certificate, MATTR VII does not issue a Certificate Signing Request (CSR) for the intermediate CA. You generate the intermediate CA's key pair and sign its certificate entirely within your own PKI, then upload the finished certificate in the next step. **Register the DTS intermediate CA certificate with MATTR VII** Register the signed intermediate CA certificate under the DTS root CA you created in the previous step. 1. Scroll down to the *Child certificates* section. 2. In the **Intermediate CA** section, select **Add new**. 3. Paste/upload the PEM-encoded DTS intermediate CA certificate into the **Certificate PEM file** field. 4. Under **Allowed child certificates**, select the signer types this intermediate CA is allowed to sign: * **VSC (VICAL Signer Certificate)** to sign a VICAL Signer. * **RSC (RICAL Signer Certificate)** to sign a RICAL Signer. Select both if the intermediate CA will sign both signer types. 5. Select **Create** to register the DTS intermediate CA certificate. Make a request of the following structure to [create a DTS intermediate CA certificate](/docs/api-reference/platform/dts-intermediate-ca-certificates/createDtsIntermediateCaCertificate) under the DTS root CA: ```http filename:"Request" POST /v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate ``` * `dtsCaCertificateId` : Replace with the `id` value obtained when you registered the unmanaged DTS root CA in the previous step. ```json filename:"Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\n...\r\n-----END CERTIFICATE-----\r\n", "usages": ["VICAL", "RICAL"] } ``` * `certificatePem` : This required parameter contains the PEM-encoded DTS intermediate CA certificate, signed by the DTS root CA. * `usages` : This required parameter specifies the intended usages for this intermediate CA. It must contain at least one value (`VICAL`, `RICAL`). Include the list type whose signer will chain to this intermediate CA (use `VICAL` for a VICAL Signer, `RICAL` for a RICAL Signer, or both if the intermediate CA will sign both). The response will include an `id` property, which is the unique identifier for the DTS intermediate CA certificate. You will use this identifier when creating the signer. **Create a VICAL Signer** Create a VICAL Signer under the DTS intermediate CA. In the 3-tier model, the VICAL Signer references the intermediate CA rather than the DTS root CA. 1. Navigate to the **Certificates** page and select the DTS root CA, then select the DTS intermediate CA you registered in the previous step. 2. In the **VSC – VICAL Signer Certificate** section, select **Add new**.\ MATTR VII creates a VICAL Signer in a *pending* state and generates a Certificate Signing Request (CSR) for it. Make a request of the following structure to [create a VICAL Signer](/docs/digital-trust-service/vical-api-reference/signers#create-a-vical-signer) that references the DTS intermediate CA certificate: ```http filename:"Request" POST /v1/ecosystems/certificates/vical-signers ``` ```json filename:"Request body" { "intermediateCaId": "080c670a-2e90-4023-b79f-b706e55e9bc6" } ``` * `intermediateCaId` : Replace with the `id` value obtained when you registered the DTS intermediate CA certificate. In the 3-tier model, the VICAL Signer references the intermediate CA rather than the DTS root CA. The response will include two properties which you will use later in this guide: * `id` : The unique identifier for the VICAL Signer. This identifier will be used in subsequent operations to reference this VICAL Signer. * `csrPem` : The X.509 Certificate Signing Request (CSR) in PEM format. You will use this CSR to generate a valid VICAL Signer Certificate (VSC) in the next step. **Generate and sign the VICAL Signer Certificate (VSC)** 1. Use the **Download** or **Copy** buttons in the **Step 1. Download the VSC Certificate Signing Request (CSR)** section of the VICAL Signer detail page to obtain the CSR. 2. Using your preferred cryptographic library or tool, generate and sign a VICAL Signer Certificate (VSC) using the CSR from the previous step. In the 3-tier model, the VSC must be signed by the DTS intermediate CA's private key. Refer to the [VSC specific requirements](/docs/digital-trust-service/certificates-overview#signer-certificate-specific-requirements-vsc-and-rsc) section for details on how to structure a valid VSC. **Associate the VSC with the VICAL Signer** Upload the signed VSC to the VICAL Signer and activate it. 1. On the VICAL Signer detail page, under **Step 2. Upload signed VSC**, paste/upload the PEM-encoded VSC into the **Certificate PEM file** field. 2. Use the *Status* radio button to set the VICAL Signer to **Active**. 3. Select **Update** to associate the VSC and activate the VICAL Signer. Make a request of the following structure to [update the VICAL Signer](/docs/digital-trust-service/vical-api-reference/signers#update-a-vical-signer) to activate and associate it with the generated VSC: ```http filename:"Request" PUT /v1/ecosystems/certificates/vical-signers/{vicalSignerId} ``` * `vicalSignerId` : Replace with the `id` value obtained when you created the VICAL Signer in the previous step. ```json filename:"Request body" { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICbzCCAhSgAwIBAgIKfS7sskyJEh+DOzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\n...\r\n-----END CERTIFICATE-----\r\n" } ``` * `active` : This required boolean indicates whether the VICAL Signer is active or not. Can only be set to `true` when a `certificatePem` is provided. Only active VICAL Signers can be used to sign VICALs. * `certificatePem` : This required parameter contains the PEM-encoded VSC created in the previous step. **Activate the DTS root CA** 1. Navigate back to the **Certificates** page in the MATTR Portal. 2. Select the DTS root CA you created in the first step. 3. Use the *Status* radio button to set the DTS root CA to **Active**. 4. Select **Update** to activate the DTS root CA. Make a request of the following structure to [update the unmanaged DTS root CA](/docs/api-reference/platform/dts-root-ca-certificates/updateDtsCaCertificate) and activate it: ```http filename:"Request" PUT /v1/ecosystems/certificates/ca/{dtsCaCertificateId} ``` * `dtsCaCertificateId` : Replace with the `id` value obtained when you registered the unmanaged DTS root CA. ```json filename:"Request body" { "active": true } ``` ### Manually Publish a VICAL [#manually-publish-a-vical] After you have set up the signing certificate chain, you can publish a VICAL that includes your participants and their associated credential types. When you publish the VICAL, the MATTR VII platform will sign a VICAL that includes the information you provided for each participant, along with the PEM-encoded IACA certificate. 1. Navigate to the **Trust lists** page under the *Digital Trust Service* section. 2. Select the **VICAL (Trusted issuers)** tab. 3. Enter a meaningful *Provider name* to identify the provider of the VICAL. This will be included in the VICAL metadata and used by relying parties to identify the source of the VICAL. 4. Select the **Create** button. 5. Review the preview area where you can see all participants and credential types included in the VICAL. 6. Select **Generate & Publish** when you are ready.\ The VICAL is now generated and published, and a modal is displayed where you can: * Use the **Download** button to download the VICAL policy file. * Use the **Copy** button to copy a link to the public endpoint where relying parties can access the policy. First, make a request of the following structure to [update the VICAL configuration](/docs/digital-trust-service/vical-api-reference/configuration#update-a-vical-configuration) and set the VICAL provider name. A VICAL configuration is required before a VICAL can be created: ```http filename:"Request" PUT /v1/ecosystems/{ecosystemId}/vicals/configuration ``` * `ecosystemId` : Replace with the `id` value of your ecosystem. ```json filename:"Request body" { "vicalProvider": "Example Provider" } ``` * `vicalProvider` : This required parameter is the provider name included in the VICAL metadata and used by relying parties to identify the source of the VICAL. Then, make a request of the following structure to [create (generate and publish) a VICAL](/docs/digital-trust-service/vical-api-reference/general#create-a-vical) based on your ecosystem policy: ```http filename:"Request" POST /v1/ecosystems/{ecosystemId}/vicals ``` The response includes the `vicalIssueID` and issuance `date` of the published VICAL. Relying parties can retrieve it from the public [Retrieve latest VICAL](/docs/digital-trust-service/vical-api-reference/general#retrieve-latest-vical) endpoint: ```bash curl -o vical-latest.cbor \ https://your-tenant.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public/latest ``` ### Configure VICAL auto-generation and publishing (optional) [#configure-vical-auto-generation-and-publishing-optional] You can optionally set up auto-generation of your VICAL so it is generated and published on a schedule. 1. Return to the **Trust lists** page under the *Digital Trust Service* section. 2. Select the **VICAL (Trusted issuers)** tab. 3. Expand the *VICAL configuration* panel. 4. Use the *Generation method* radio button to select **Auto generate**. 5. Use the *Auto generate frequency* dropdown list to select how often you want the VICAL to be automatically generated and published (daily/weekly). 6. Select the **Update** button. 7. Review the preview area where you can see all participants and credential types included in the VICAL. Note that the VICAL is not generated and published yet. It will only be generated and published automatically based on the frequency you selected in step 5 above. If you want to generate and publish the VICAL immediately, you can select the **Generate & Publish** button. Make a request of the following structure to [update the VICAL configuration](/docs/digital-trust-service/vical-api-reference/configuration#update-a-vical-configuration) and enable scheduled auto-publishing: ```http filename:"Request" PUT /v1/ecosystems/{ecosystemId}/vicals/configuration ``` ```json filename:"Request body" { "vicalProvider": "Example Provider", "autoPublish": { "enabled": true, "frequency": "Daily" } } ``` * `autoPublish.enabled` : Set to `true` to enable scheduled automatic generation and publishing of the VICAL. * `autoPublish.frequency` : Required when `enabled` is `true`. How often the VICAL is automatically generated and published. One of `Daily` or `Weekly`. When auto-publishing is enabled, the VICAL is generated and published automatically on the schedule you set. You can still generate and publish a VICAL immediately at any time by calling the [Create a VICAL](/docs/digital-trust-service/vical-api-reference/general#create-a-vical) endpoint. ### View Previously Published VICALs (optional) [#view-previously-published-vicals-optional] 1. Return to the **Trust lists** page under the *Digital Trust Service* section. 2. Select the **VICAL (Trusted issuers)** tab. 3. Scroll down and select the **View Previously Published** button to see all previously published VICALs. 4. Use the **Download** button to download the policy file for any previously published VICAL displayed. Make a request of the following structure to [retrieve all VICALs](/docs/digital-trust-service/vical-api-reference/general#retrieve-all-vicals) published in your ecosystem. This endpoint is public and does not require authentication: ```http filename:"Request" GET /v1/ecosystems/{ecosystemId}/vicals/public ``` The response is a JSON list of the published VICALs with their `vicalIssueID`, issuance `date`, and `filename`. To download a specific VICAL policy file (a CBOR-encoded file), call the [Retrieve a VICAL](/docs/digital-trust-service/vical-api-reference/general#retrieve-a-vical) endpoint with the relevant `vicalIssueId`: ```bash curl -o vical.cbor \ https://your-tenant.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public/{vicalIssueId} ``` ## Next steps [#next-steps] Now that you have published your VICAL, you can share the public endpoint with relying parties so they can consume the VICAL and establish trust in the issuers and credential types included in it. You can also refer to the [VICAL consumption guide](/docs/digital-trust-service/vical-consumption) to learn how relying parties can consume, validate and use a VICAL. # What is a VICAL and how does it work? URL: /docs/digital-trust-service/vical-overview ## Introduction [#introduction] A VICAL (Verified Issuer Certificate Authority List) is a mechanism defined in [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) Annex C to support establishing trust in digital ecosystems where relying parties need to verify mDocs issued by numerous different issuers. For example, consider the case of relying parties that need to verify Mobile Driver's Licenses (mDLs). Different states and/or provinces can issue their own mDLs, each signed by a Document Signer Certificate (DSC) that is itself signed by the state/province unique Issuing Authority Certificate Authority (IACA). If you’re someone who needs to verify these mDLs (like a police officer or a business verifying identities), you would have to individually assess and trust each state/province IACA. This can become complicated, especially as the number of issuers grows, because you'd have to create and manage a lot of different trust relationships. A VICAL solves this by collecting and validating IACAs from different issuing authorities, and then cryptographically signing them into a single list. Each IACA in the VICAL is associated with: * An issuing authority that can use this IACA as the root certificate when signing mDocs. * Credential types for which this IACA can serve as a root certificate. When a relying party trusts a VICAL they can trust any presented mDoc, given that: * The credential was issued by an issuing authority that is included in the VICAL. * The root certificate (IACA) of the chain of certificates used to sign the credential matches the IACA that is associated with this issuing authority in the VICAL. * The credential type matches one of the credential types associated with this IACA in the VICAL. This mechanism enables relying parties to verify mDocs (such as mDLs) from any issuing authority included in the VICAL without managing multiple separate trust relationships with each issuing authority. This can greatly simplify the process of verifying mDocs in complex ecosystems, such as verifying mDLs across various jurisdictions. A VICAL can be considered a standardized approach to creating a [Digital Trust Service (DTS)](/docs/digital-trust-service). ## VICAL roles [#vical-roles] * **VICAL Provider**: Operates the VICAL and provides it as a service to different ecosystem participants. The VICAL provider collects and validates information from relevant issuers, compiles it into a standardized VICAL format and distributes it to relying parties. * **Issuers**: Issue mDocs to holders while attesting the validity of claims included in these credentials. * **Relying parties**: Consume the VICAL and use issuers' information to verify presented mDocs. ## VICAL components [#vical-components] * **VICAL metadata:** This includes general information about the VICAL itself: * Version. * Provider. * Issuance date. * Unique identifier. * Next update. * **VICAL records:** Each record includes the following information that can be used by relying parties to establish trust in mDocs and the IACAs that are used as their root certificate: * A trusted IACA that can be used as a root certificate to sign mDocs. * Issuing authority that can issue mDocs with this IACA. * Credential types that this IACA can serve as a root certificate for, listed in the `docType` array. A single VICAL record can authorize an IACA for one or more docTypes. * Additional IACA information: * Validity period. * Unique identifier. * State/Province. * Country. * Public key info (algorithm, curve and value). * Signature info (algorithm and value). * Fingerprints. * Extensions used. ## VICAL structure [#vical-structure] The decoded VICAL payload follows the CDDL definition below. Each entry in `certificateInfos` authorizes a single IACA to sign mDocs of one or more `docType` values. ```text VICAL = { "version" : tstr, ; VICAL structure version, currently "1.0" "vicalProvider" : tstr, ; Identifies the VICAL provider "date" : tdate, ; date-time of VICAL issuance "vicalIssueID" : uint, ; Uniquely identifies this specific issue of the VICAL ? "nextUpdate" : tdate, ; Optional: date-time of the next planned VICAL update "certificateInfos" : [+ { "certificate" : bstr, ; DER-encoded X.509 certificate (IACA) "serialNumber" : biguint, ; Serial number of the certificate "ski" : bstr, ; Subject Key Identifier "docType" : [+ tstr], ; docTypes this IACA is authorized to sign ? "issuingAuthority" : tstr, ; Issuing authority name ? "issuingCountry" : tstr, ; ISO 3166-1 alpha-2 country code ? "stateOrProvinceName" : tstr, ; State or province (sub-national issuers) ? "notBefore" : tdate, ; Certificate validity start ? "notAfter" : tdate ; Certificate validity end }] } ``` ## How it works [#how-it-works] VICAL trust model 1. The VICAL provider establishes its own root certificate with an associated Public Key Infrastructure (PKI) chain of certificates, based on the [chain of trust](/docs/concepts/chain-of-trust) model. 2. The VICAL provider collects and validates IACAs from different issuing authorities. Each of these IACAs are vetted by the VICAL provider and trusted to issue mDocs of specific credential types. 3. The VICAL provider uses their chain of trust end-entity certificate to sign the valid IACAs into a single list. 4. Each issuing authority uses their own IACA and associated PKI chain of certificates to sign mDocs. 5. Relying parties can consume the VICAL in one of two ways: * Download the VICAL directly from the provider’s website. * Retrieve the VICAL via an endpoint exposed by the provider as an API. 6. When a relying party attempts to verify an mDoc, they validate its signature and referenced PKI certificate chain against the VICAL to ensure: * This issuing authority can use this IACA as a root certificate when signing mDocs. * This credential type can use this IACA as a root certificate. 7. Upon successful validation, the relying party can verify the presented mDoc without having to create and manage a trust relationship directly with its issuing authority. Want to see a live VICAL from the inside? Check out the [MATTR Labs VICAL Viewer](https://tools.mattrlabs.com/vical-viewer) that can render existing VICAL `.cbor` files into a human-readable format. ## Linked certificates [#linked-certificates] A **linked certificate** lets a participant rotate the IACA certificate they use in the ecosystem without breaking trust for relying parties that already trust the previous certificate. When you add a new certificate for a participant, you can add it as a **successor** to a previously uploaded certificate by selecting the **predecessor** and uploading a **link certificate** that ties the new certificate to the previous one. This preserves trust continuity for the participant across the rotation, so relying parties can continue to validate the participant's mDocs without first having to consume an updated VICAL. To add a linked certificate, refer to the [VICAL guide](/docs/digital-trust-service/vical-guide). # Capabilities URL: /docs/concepts/capabilities MATTR [platforms](/docs/concepts/platforms) offer versatile capabilities, crafted from a suite of services designed to effectively support solutions across any use case: MATTR capabilities * **[Credential issuance](/docs/issuance)**: Generation, distribution and lifecycle management of digital verifiable credentials. * **[Credential holding](/docs/holding)**: Tools and functionalities that support secure storage, management, and usage of digital credentials and assets. * **[Credential verification](/docs/verification)**: Validation of digital credentials against established criteria and protocols, ensuring their legitimacy and relevance for access or transactional purposes. * **[Digital Trust Service](/docs/digital-trust-service)**: Technical orchestration and management of digital trust frameworks. * **[Platform management](/docs/platform-management)**: Oversight of MATTR platforms infrastructure and services, ensuring operational efficiency, security, and scalability. Some capabilities can be achieved by more than one platform. Choosing the right platform for you depends on your use case, requirements, resources and implementation timelines. [Contact us](http://mattr.global/contact) to discuss your options. # Chain of trust URL: /docs/concepts/chain-of-trust Description: Learn how certificates form a chain of trust within MATTR's capabilities, providing assurance needed to validate digital artifacts such as credentials, identities, and verification requests. ## Overview [#overview] Certificates are used as a foundation of trust within MATTR’s capabilities. They provide the assurance needed to validate digital artifacts—whether that’s an issued credential, an identity, a verification request, or a trusted list. These certificates are verified based on a chain of trust model, where each certificate is linked to its issuer via a series of certificates: * **Root certificate**: This digital certificate belongs to the Certificate Authority (CA), and as its name implies, is at the root of the trust chain. Any certificate along the chain of trust must be linked to the root certificate. * **Intermediate certificate**: This digital certificate is linked to the root certificate and acts as an intermediary between the root certificate and the end-entity, or between other intermediate certificates. The final certificate in the chain of certificates is referred to as a *leaf certificate* or an *end-entity certificate*, and is being used to sign the end-entity. * **End-entity**: This is the final object that is being signed by the leaf/end-entity certificate (the last certificate in the chain of certificates). For example, when an issuer signs a credential, the end-entity is the credential itself. When a verifier requests a verification of a credential, the end-entity is the verification request. For example, this is how the chain of trust model is applied to verify the identity of an issuer signing an mDoc: Chain of trust The *root certificate* is the **Issuer Authority Certificate Authority (IACA)**. It is used to sign a **Document Signer Certificate (DSC)**, which is the *leaf/end-entity certificate*. The DSC is then used to sign the **Mobile Security Object (MSO)** in the issued mDoc, which is the *end-entity*. ## Key benefits [#key-benefits] This model offers the following advantages: * **Scalability**: It allows trust to be extended from a trusted authority down to other entities or components. For example, a root certificate authority (CA) can delegate trust to intermediate CAs, which can further delegate trust to end-user certificates. This makes it easier to manage trust across a large network or ecosystem of trust relationships. * **Security**: It implements multiple layers of security, with each layer depending on the security of the previous one. This approach can deter attackers and provide more opportunities to detect and mitigate breaches. If one component fails or is compromised, the impact can be isolated, thereby minimizing the impact and preventing further spread of the breach. * **Transparency and auditability**: Every step in the chain is typically documented and can be audited, which makes it easier to track where trust was delegated and to ensure that trust relationships have been correctly established and maintained. This transparency is crucial for compliance and security audits. * **Interoperability**: The chain of trust is a well-understood and widely adopted model in various security standards, such as SSL/TLS for web security, PKI (Public Key Infrastructure) for digital certificates, and blockchain technologies. This makes it easier to integrate with other systems and ensures compatibility across different platforms. Overall, the chain of trust model provides a robust, scalable, and manageable way to establish and maintain trust across various components of a digital ecosystem, making it a cornerstone of modern security architectures. ## External certificates [#external-certificates] MATTR VII offers a robust out-of-the-box solution for certificate management. This includes generation, storage, and usage of signing certificates, where all the associated private and public keys managed securely within MATTR’s Key Management System (KMS). However, some customers prefer (or are required to) manage their own certificates required for different digital credential workflows. External certificates support in MATTR VII offers flexibility to comply with specific Public Key Infrastructure (PKI) requirements. It preserves the integrity of the trust chain while giving customers full control over their cryptographic materials. The following table depicts the differences between managed and unmanaged certificates in MATTR VII: | Internal (Managed) Certificates | External (Unmanaged) Certificates | | :--------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | | MATTR VII provisions and manages all required certificates | MATTR VII provides tools to assist customers in managing their own certificates | | All private keys are managed by MATTR VII | Customers manage the root CA private key, while MATTR VII manages intermediate and end-entity certificates keys | | MATTR VII automatically generates and signs certificates required for signing operations | MATTR VII integrates external certificates provided by the customer into signing workflows | | Certificates are automatically validated and managed by MATTR VII | Customers must ensure their certificates meet MATTR’s validation requirements | | MATTR VII handles all certificate lifecycle management tasks | Customers must manage the lifecycle of their own certificates | In both models, in every signing operation MATTR VII must be able to associate and understand: * Who is performing the signing: This is an internal MATTR entity that represents a signer. It contains the signer’s unique identifier and (if managed) the private key in KMS. * What certificate is associated with that signer: This is a X.509 certificate that links the signer’s public key to its identity. In the external (unmanaged) certificate flow, the customer is responsible for creating and uploading this certificate to MATTR VII, so it can be associated with the corresponding signer entities. * What root certificate authority (CA) issued the certificate: This is the root CA that issued the certificate associated with the signer. In the external (unmanaged) certificate flow, the customer is responsible for registering this root CA in MATTR VII, so it can be used to validate the certificate chain of trust. The **signer** is like a digital "signing machine" within MATTR, whereas the **signer certificate** is the official "license" issued by a trusted authority stating the signer is valid and authorized. ### Enabling external certificates [#enabling-external-certificates] The following steps outline the process of enabling and using external certificates in MATTR VII: 1. **Root CA certificate generation**: The customer generates a private/public key pair and uses the private key to sign a self-signed root certificate. 2. **Unmanaged root CA certificate registration**: The customer uses a MATTR VII endpoint to register the unmanaged root CA certificate by providing it in PEM format, which includes the public key and other necessary information. This root CA certificate is not managed by MATTR VII, meaning MATTR will not store the private key or handle its lifecycle. A root CA certificate identifier is generated by MATTR VII and is used to reference this unmanaged root CA certificate in subsequent operations. 3. **Signer creation**: The customer uses a MATTR VII endpoint to create a signer which references the unmanaged root CA certificate identifier. 4. **Certificate Signing Request (CSR)**: When a signer is created, MATTR VII generates a new private/public key pair: * The *private* key is stored in the MATTR KMS and used to sign a CSR. * The *public* key is included in the CSR, alongside all necessary information to enable the customer to issue a valid certificate that meets MATTR’s requirements. 5. **Signer certificate generation**: The customer uses the CSR to generate and sign a signer certificate which meets the following requirements: * Must include the public key from the CSR. * Must be signed by the root CA certificate private key. * Must match the structure and values expected by MATTR, as described in the CSR. 6. **Certificate upload**: After the signer certificate is signed, the customer uses a MATTR VII endpoint to upload it in PEM format and associate it with the signer. 7. **Certificate validation**: MATTR VII will validate the uploaded certificate to ensure it is signed by the expected root CA certificate, contains the correct public key, and includes the necessary X.509 extensions and usages. 8. **Signer and root CA certificate activation**: Once the certificate is validated, the signer, followed by the registered unmanaged root CA certificate, can be activated, allowing them to be used in signing operations. MATTR VII will not select any signers if their referenced root CA certificate is not active. 9. **Certificate lifecycle management**: The customer is responsible for managing the lifecycle of the external certificates, including renewal and revocation. MATTR VII will not automatically handle these tasks for unmanaged root CA certificates. ## Certificates rotation [#certificates-rotation] To support certificates rotation, a single MATTR VII tenant can manage multiple root CA certificates. This approach allows issuers to distribute new root CAs in advance, ensuring seamless continuity of relying party reliance over their trust frameworks. The process involves creating a new root CA certificate without activating it, which means it will not be used by MATTR VII in any sign operations. However, this inactive root CA can be shared with relying parties ahead of its activation. When ready, the customer can activate the new root CA which automatically deactivates the old one. After this switch, new signer certificates will be signed using the new root CA, and relying parties will be able to verify end-entities signed by these signer certificates immediately, as the root CA was pre-distributed to them. Root CA rotation should be performed as follows: 1. Identify that the current active root CA (**root CA #1**) must be rotated as it is about to expire. 2. Create a new **root CA #2**. 3. Distribute **root CA #2** to all relying parties. Step #3 must be performed out-of-band. Creating a new root CA on MATTR VII doesn't update any existing trusted lists which include this root CA. 4. Set **root CA #2** to `active`. ## Frequently asked questions [#frequently-asked-questions] ### What is a chain of trust? [#what-is-a-chain-of-trust] A chain of trust is a sequence of digital certificates that link an end-entity, such as a signed credential, back to a trusted root certificate authority. Each certificate in the chain is cryptographically signed by the one above it, allowing a verifier to confirm that the end-entity was signed by an authority that ultimately traces back to a recognised trust anchor. ### What is the role of the root certificate? [#what-is-the-role-of-the-root-certificate] The root certificate belongs to a Certificate Authority (CA) and sits at the top of the trust chain. It is self-signed and is the anchor that every other certificate in the chain must link back to. Relying parties are configured to trust specific root certificates, and that trust is what makes the rest of the chain meaningful. ### What is the difference between an internal and an external certificate in MATTR VII? [#what-is-the-difference-between-an-internal-and-an-external-certificate-in-mattr-vii] Internal (managed) certificates are generated and managed entirely by MATTR VII, including all private keys held in MATTR's Key Management System. External (unmanaged) certificates are managed by the customer, who controls the root CA private key while MATTR VII manages intermediate and end-entity certificate keys. External certificates are useful when customers have specific Public Key Infrastructure requirements. ### Why does a chain of trust matter for verifiable credentials? [#why-does-a-chain-of-trust-matter-for-verifiable-credentials] It lets a verifier confirm that a credential was signed by an authority that is recognised under the relevant trust framework without contacting the issuer at the time of verification. The cryptographic chain provides offline, deterministic proof of issuer authenticity. ### How are root CAs rotated without breaking verification? [#how-are-root-cas-rotated-without-breaking-verification] A new root CA is created and distributed to relying parties before it is activated. Once activated, the previous root CA is automatically deactivated and new signer certificates are issued under the new root. Because relying parties already have the new root, verification continues without interruption. # What is a verifiable credential ecosystem? URL: /docs/concepts/credential-ecosystem Description: Learn how a verifiable credential ecosystem works, including the roles of issuer, holder, verifier, and trust framework, and how they interact to enable secure digital credential exchange. This page explains how a **verifiable digital credential ecosystem** works. If you are new to digital credentials, read [Verifiable credentials](/docs/concepts/verifiable-credentials) first. That page explains what a verifiable credential is and why cryptographic verification matters. This page builds on that foundation by focusing on the **ecosystem roles**, the **trust layer**, and the **interaction flow** between participants. ## What is a verifiable credential ecosystem? [#what-is-a-verifiable-credential-ecosystem] A **verifiable credential ecosystem** is the set of participants, rules, and technical components that allow digital credentials to be: * issued by a trusted organisation * held by an individual or organisation * presented when proof is needed * verified by another party * trusted across systems, organisations, and jurisdictions At a minimum, most ecosystems include four core roles: * **Issuer**: creates and signs the credential * **Holder**: receives, stores, and presents the credential * **Verifier**: requests and checks the credential * **Trust framework** or **trust registry**: provides the governance and trust signals that help participants decide which issuers, wallets, keys, and rules to trust These roles work together to move trust from fragmented, manual checks into a reusable digital model. ## Why this ecosystem model matters [#why-this-ecosystem-model-matters] A verifiable credential is not useful on its own. Its value comes from being part of an ecosystem where multiple parties can rely on it consistently. That ecosystem model matters because it helps participants answer four essential questions: 1. **Who issued this credential?** 2. **Can I trust that issuer?** 3. **Has the credential been changed, expired, or revoked?** 4. **Is the holder sharing the right information for this transaction?** A well-designed ecosystem makes those questions easier to answer in a secure, privacy-aware, and scalable way. ## The four core roles in a verifiable credential ecosystem [#the-four-core-roles-in-a-verifiable-credential-ecosystem] The actors in a verifiable credential ecosystem and how a credential flows from issuer to holder to verifier ### Issuer [#issuer] An **issuer** is the organisation that creates and signs a credential. The issuer is the source of the claims inside the credential. For example, an issuer might be: * a government issuing a mobile driver’s licence * a university issuing a degree credential * a bank issuing a proof-of-account or customer credential * an employer issuing an employee ID credential The issuer is responsible for: * confirming the underlying data is accurate * defining what the credential contains * signing the credential so others can verify its authenticity * managing status over time, such as expiry, suspension, or revocation * operating within the rules of the relevant trust framework #### What value does the issuer gain? [#what-value-does-the-issuer-gain] Issuers benefit because they can provide credentials that are: * **portable**, so the holder can reuse them across services * **tamper-evident**, because the credential is cryptographically signed * **easier to verify**, reducing follow-up checks and manual validation * **more interoperable**, when aligned to shared standards and trust rules For issuers, this can reduce operational overhead, improve service delivery, and extend the usefulness of authoritative data beyond a single channel or portal. ### Holder [#holder] A **holder** is the person or organisation that receives and controls the credential. The holder typically stores credentials in a wallet application and chooses when to present them. The holder is not the source of the credential’s authority. Instead, the holder is the party that has been given the credential and can use it to prove something about themselves. Examples include: * a citizen holding a mobile driver’s licence * a student holding a degree credential * an employee holding a digital staff ID * a business holding a licence or accreditation credential The holder is responsible for: * receiving and storing the credential * keeping access to it secure * reviewing presentation requests * deciding when to share it * consenting to the disclosure of requested information #### What value does the holder gain? [#what-value-does-the-holder-gain] Holders benefit because verifiable credentials can give them: * **more control** over when and where their information is shared * **more convenience** across repeated interactions * **better privacy**, especially when selective disclosure is supported * **less repetition**, because the same credential can be reused across services * **greater transparency**, because they can see what is being requested For holders, the ecosystem works best when trust does not require oversharing. ### Verifier [#verifier] A **verifier** is the organisation that requests and checks a credential. The verifier relies on the credential to make a decision, such as whether to: * allow access to a service * approve an application * confirm age or identity * validate a qualification or licence * onboard a customer or employee The verifier is responsible for: * requesting the right information for the interaction * validating the credential’s authenticity and integrity * checking whether the issuer is trusted under the relevant framework * checking status information such as expiry or revocation * applying business or policy rules to the verified result #### What value does the verifier gain? [#what-value-does-the-verifier-gain] Verifiers benefit because verifiable credentials can provide: * **stronger assurance**, through cryptographic verification * **faster decisions**, with less manual review * **reduced fraud risk**, because tampering is detectable * **better privacy alignment**, by collecting only what is needed * **less integration complexity**, when ecosystems use shared standards and trust rules For verifiers, the key value is not just seeing data, but being able to trust where it came from and whether it is still valid. ### Trust framework or trust registry [#trust-framework-or-trust-registry] A verifiable credential ecosystem also needs a trust layer. This is often described as a **trust framework**, and in some implementations it is supported by a **trust registry**. A **trust framework** is the set of legal, policy, operational, and technical rules that define how the ecosystem works. It answers questions such as: * who is allowed to issue credentials * what standards or schemas must be followed * what assurance requirements apply * how participants are onboarded and governed * how keys, identifiers, and trust anchors are managed * how disputes, compliance, and liability are handled A **trust registry** is a technical mechanism that can publish or make available trust information used by ecosystem participants. Depending on the ecosystem, it may contain information about: * trusted issuers * trusted verifier organisations * approved wallet providers * public keys or trust chains * supported schemas or credential types * accreditation or governance status #### Why distinguish trust framework from trust registry? [#why-distinguish-trust-framework-from-trust-registry] These terms are related, but they are not the same. * A **trust framework** is the broader governance model. * A **trust registry** is one technical way to expose trust information inside that model. Some ecosystems may use a formal registry. Others may rely on federation metadata, certificate chains, government trust lists, or another trust distribution mechanism. #### What value does the trust layer provide? [#what-value-does-the-trust-layer-provide] The trust layer benefits everyone: * **Issuers** gain a recognised path to ecosystem acceptance * **Holders** gain confidence that their credentials will be widely usable * **Verifiers** gain a reliable way to determine who and what to trust * **Ecosystem operators** gain a scalable way to govern participation and assurance Without a trust layer, a verifier may be able to confirm that a credential was signed, but not whether the signer should be trusted for that credential type. ## Credential lifecycle [#credential-lifecycle] Credential lifecycle refers to the stages a credential goes through from creation to eventual expiry or revocation. Understanding this lifecycle is key to building secure and privacy-preserving digital credential solutions. At a high level, the interaction flow is simple: 1. The issuer creates and signs a credential 2. The holder receives and stores it 3. The verifier requests proof from the holder 4. The holder presents the credential or a derived proof 5. The verifier checks the credential, the issuer trust chain, and status information 6. The verifier makes a decision The trust framework sits around this exchange and defines the rules that make the result meaningful. Beyond issuance and presentation, credential lifecycle events may include updates, revocation or expiry. The ecosystem must support these lifecycle events to maintain trust over time: Credential lifecycle * Credential update: The issuer may need to update a credential, such as adding new claims or correcting information. The ecosystem should support a secure way to issue an updated credential and manage the lifecycle of the old one. * Credential revocation: If a credential is compromised, no longer valid, or the holder’s relationship with the issuer changes, the issuer may need to revoke it. The ecosystem should support a way for verifiers to check revocation status during verification. * Credential expiry: Some credentials are only valid for a certain period. The ecosystem should support expiry information and ensure verifiers can check it. ### Ecosystem flow at a glance [#ecosystem-flow-at-a-glance] ### What happens during issuance? [#what-happens-during-issuance] Issuance is the process where an issuer creates a credential for a specific holder. During issuance, the issuer typically: * confirms the subject’s eligibility * prepares the credential data * signs the credential using its cryptographic keys * delivers the credential to the holder’s wallet * records or publishes any needed status information The result is a credential that the holder can later present. #### Issuance flow [#issuance-flow] ### What happens during presentation and verification? [#what-happens-during-presentation-and-verification] Presentation is the process where the holder shares proof with a verifier. Verification is the process where the verifier checks that proof and decides whether it can rely on it. During this interaction, the verifier may check: * whether the credential is authentic * whether the issuer is trusted * whether the credential is expired or revoked * whether the proof matches the requested data * whether holder binding or other policy requirements are satisfied The holder may present: * the full credential * selected attributes * a derived proof, depending on the credential format and ecosystem design #### Verification flow [#verification-flow] ## What each role needs from the others [#what-each-role-needs-from-the-others] Thinking in architecture terms, each role depends on the others in different ways. ### What the issuer needs [#what-the-issuer-needs] The issuer needs: * a way to identify the holder * a credential format and schema * signing keys and trust anchor relationships * a delivery channel to the holder * a status management approach * governance rules that define when it is authorised to issue ### What the holder needs [#what-the-holder-needs] The holder needs: * a wallet or holder application * a secure way to receive credentials * a way to review requests from verifiers * a way to present credentials or proofs * confidence that the ecosystem protects privacy and usability ### What the verifier needs [#what-the-verifier-needs] The verifier needs: * a way to request the right proof * a way to validate signatures and credential structure * access to trust information * access to status information when required * policy rules that define what is acceptable for the use case ### What the trust layer needs [#what-the-trust-layer-needs] The trust layer needs: * governance and operating rules * participant onboarding and accreditation processes * trust anchor management * mechanisms to publish or distribute trust information * change management across standards, participants, and assurance requirements ## What makes an ecosystem trustworthy? [#what-makes-an-ecosystem-trustworthy] A verifiable credential ecosystem is not trustworthy just because it uses cryptography. It becomes trustworthy when technical verification is combined with governance. That usually includes: * cryptographic integrity, so credentials cannot be altered undetectably * issuer authenticity, so verifiers know who signed the credential * status management, so expired or revoked credentials can be identified * trust anchors and frameworks, so verifiers know which issuers are accepted * privacy-preserving presentation, so only necessary data is shared * clear ecosystem rules, so participants understand obligations and assurance levels This is why trust frameworks matter so much. Cryptography can prove that a credential was signed. Governance determines whether that signature should be relied on in a real-world context. ## Centralised verification versus ecosystem trust [#centralised-verification-versus-ecosystem-trust] Traditional verification often depends on a central database lookup each time proof is needed. A verifiable credential ecosystem works differently. The proof travels with the credential, and the verifier can often validate it independently using cryptographic proofs, trust metadata, and status information. This changes the architecture in important ways: * trust becomes more portable * verification can be more scalable * privacy can improve because fewer central lookups are required * interoperability becomes more achievable across organisations The result is not the removal of trust, but the distribution of trust into a framework that can be checked and reused more consistently. ## Example: how the ecosystem works for a mobile driver’s licence [#example-how-the-ecosystem-works-for-a-mobile-drivers-licence] A mobile driver’s licence ecosystem might work like this: * A transport authority acts as the issuer * A citizen acts as the holder * A police officer, retailer, or service provider acts as the verifier * A government trust framework defines which issuers, keys, wallets, and policies are accepted In practice: * The transport authority issues an mDL to the citizen * The citizen stores it in a wallet * A verifier requests proof of identity, age, or driving entitlement * The citizen presents the required information * The verifier checks the proof, issuer trust, and status * The verifier accepts or rejects the interaction based on policy That same model can apply to other credential types, including education, health, workforce, and financial use cases. ## Frequently asked questions [#frequently-asked-questions] ### What is a verifiable credential ecosystem? [#what-is-a-verifiable-credential-ecosystem-1] A verifiable credential ecosystem is the set of participants, rules, and technical components that allow digital credentials to be issued by a trusted organisation, held by an individual or organisation, presented when proof is needed, verified by another party, and trusted across systems, organisations, and jurisdictions. Most ecosystems include four core roles, issuer, holder, verifier, and a trust framework or trust registry. ### What is the difference between an issuer and a verifier? [#what-is-the-difference-between-an-issuer-and-a-verifier] An issuer creates and signs a credential. A verifier requests and checks it. The issuer is the source of the claims inside the credential. The verifier relies on the credential to make a decision, such as whether to grant access or approve an application. ### Is the holder always a person? [#is-the-holder-always-a-person] No. In many ecosystems the holder is a person, but it can also be an organisation, device, or software agent, depending on the use case and trust model. ### What is the difference between a trust framework and a trust registry? [#what-is-the-difference-between-a-trust-framework-and-a-trust-registry] A trust framework is the broader governance model for the ecosystem. It defines the legal, policy, operational, and technical rules. A trust registry is one technical mechanism that can publish trust information used within that framework, such as the list of accredited issuers or trusted keys. ### Can a verifier trust any digitally signed credential? [#can-a-verifier-trust-any-digitally-signed-credential] No. A verifier must usually confirm both that the credential is cryptographically valid and that the issuer is trusted for that credential type under the relevant framework. A valid signature alone does not establish that the signer is the right authority to issue the credential. ### Does the issuer need to be online every time a credential is verified? [#does-the-issuer-need-to-be-online-every-time-a-credential-is-verified] Not always. In many verifiable credential models, the verifier can validate the credential without contacting the issuer directly for every transaction, although status checks or trust resolution may still be required depending on the design. ### Why is the trust layer necessary? [#why-is-the-trust-layer-necessary] Without a trust layer, a verifier may know that a credential was signed, but not whether the signer is recognised, authorised, or governed appropriately for the use case. The trust framework and trust registry provide the governance signals that make ecosystem-wide verification meaningful. ## Summary [#summary] A verifiable digital credential ecosystem is made up of issuers, holders, verifiers, and a trust layer that governs how they interact. * Issuers create and sign trusted credentials * Holders receive, store, and present them * Verifiers request and validate proofs * Trust frameworks and trust registries provide the governance and trust signals that make ecosystem-wide verification possible Together, these roles allow trust to move with the credential, rather than staying trapped in isolated databases and manual checks. # What is a decentralized trust model? URL: /docs/concepts/decentralized-trust-model Description: Learn how decentralized digital trust differs from earlier identity models, the technology that enables it, and the mind shifts required for policy makers and stakeholders. Digital trust is moving from a federated model, in which intermediaries authenticate users and assert their identity to relying parties, to a decentralized one, in which the holder receives verifiable credentials and presents them directly. This shift is not a technology upgrade. It is a change in the architecture of trust itself. Several jurisdictions are operationalizing decentralized digital identity programs, including the European Union (eIDAS 2.0 and the European Digital Identity Wallet), Australia, New Zealand, the United Kingdom, Canada, and Singapore. The standards stack is mature, the architectural model is widely understood, and credential schemes are entering production. The questions that remain are governance questions, and they require policy makers, regulators, and ecosystem stakeholders to reframe assumptions formed in earlier eras. This page explains what is genuinely different about the decentralized trust model, the technology that enables it, and the shifts in thinking required to govern it well. ## The three architectural eras of digital identity [#the-three-architectural-eras-of-digital-identity] ### Era one: Paper and the trusted holder [#era-one-paper-and-the-trusted-holder] For most of the twentieth century, identity verification meant presenting a physical document. Governments issued passports, birth certificates, and driver licenses. The citizen carried them. A relying party (a bank, an employer, or a border officer) inspected the document, made a judgment about its authenticity, and recorded what they needed. The safeguards in this era were operational: document security features, controlled issuance, in-person inspection. What happened at the point of presentation was largely outside the issuer's view. This era's privacy properties were accidental rather than designed. There was no national identity database because there was no practical way to build one. Each relying party kept its own records, and linkage between them was difficult. ### Era two: Federated identity and the trusted intermediary [#era-two-federated-identity-and-the-trusted-intermediary] From the early 2000s, governments and large institutions moved identity verification online. The architecture that emerged was **federated**. A single trusted intermediary, the identity provider, authenticated the user and then asserted to relying parties that the user was who they claimed to be. National schemes followed this model, alongside consumer-grade equivalents such as Login with Google and Login with Apple. The federated model solved a real problem. It removed the need for every relying party to verify identity independently. But it introduced a new one: **the identity provider sees every transaction.** Each verification request flows through the intermediary, which can build a complete view of which services a person accesses. The safeguards in this era were contractual and regulatory. Identity providers were required to handle data carefully, retain it only as long as necessary, and submit to oversight. Privacy law and binding terms did the work. But the architecture itself was surveillance-capable by design: the intermediary had to see the transaction in order to authenticate it. ### Era three: Decentralized credentials and the trusted holder, returned [#era-three-decentralized-credentials-and-the-trusted-holder-returned] The decentralized model returns the holder to the center, but with cryptographic guarantees the paper era could not offer. An authoritative issuer issues a [verifiable credential](/docs/concepts/verifiable-credentials) to the holder's wallet. The holder presents that credential to relying parties directly, without an intermediary in the transaction loop. The credential is cryptographically signed by the issuer, so the relying party can verify its authenticity without contacting the issuer. The issuer cannot see where the credential is being used. What is genuinely different about era three is not the cryptography itself. It is the consequence: **no party in the system holds a complete record of how a person uses their identity.** Not the issuer. Not a federation provider. Not a single trust registry. The data minimization that was accidental in era one and contractual in era two is now **architectural** in era three. ## The technology that enables decentralized trust [#the-technology-that-enables-decentralized-trust] The decentralized trust model rests on a stack of open, internationally maintained standards that have matured significantly over the last five years. | Layer | Standard | Role | | -------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------ | | Credential data model | W3C Verifiable Credentials Data Model 2.0 | Defines the structure and semantics of a verifiable credential | | mobile Driver’s License (mDL) | ISO/IEC 18013-5 | Specifies the data model and presentation protocol for mobile driving licenses (mDL) | | Selective disclosure credentials | SD-JWT VC | A credential format that supports presenting only selected attributes | | Presentation protocol | OpenID for Verifiable Presentations (OID4VP) | Defines how a relying party requests and receives credentials from a wallet | | Issuance protocol | OpenID for Verifiable Credential Issuance (OID4VCI) | Defines how an issuer delivers credentials to a wallet | | Browser integration | W3C Digital Credentials API | Allows web pages to request credentials from a wallet through the browser | Together, these standards make it possible for an issuer to mint a credential once, for a holder to store and present it from any compliant wallet, and for a verifier to validate it independently without contacting the issuer or any central intermediary. Two properties of this stack matter particularly for governance: * **Cryptographic signatures** make credentials tamper-evident and bind them to the issuer without requiring a live lookup at verification time. The verifier can confirm authenticity offline. * **Selective disclosure** lets the holder present only the specific facts a verifier needs (for example, "over 18" rather than a full date of birth). This is how the architecture enforces data minimization in practice. See [Selective disclosure](/docs/concepts/selective-disclosure) for more. Trust is anchored in cryptographic keys and governed by explicit trust frameworks rather than inferred from reputation or jurisdiction. See [Chain of trust](/docs/concepts/chain-of-trust) for how issuer keys are anchored to recognized trust roots. ## Four layers of a credential ecosystem [#four-layers-of-a-credential-ecosystem] It is helpful to read a credential ecosystem in terms of four functional layers, because the policy questions tend to sit cleanly within one or another of them. This framing is not specific to any single jurisdiction. It is a useful way to read what is actually happening in deployed programs. The EU's Architecture and Reference Framework, the ISO 18013 family of standards, and most national trust frameworks make similar distinctions, sometimes with different vocabulary. The actors in a verifiable credential ecosystem and how a credential flows from issuer to holder to verifier | Layer | What it does | Where it sits | | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Issuing authority** | Decides what may be asserted, at what level of assurance, and under what conditions. Holds the revocation mandate. | The agency with the legal standing and the authoritative source data to create credentials. | | **Issuance platform** | Turns the issuing authority's instructions into cryptographically signed credentials and delivers them to the holder's wallet. | The technical infrastructure. It has no independent credential authority. It does what the issuing authority tells it to do, to a standards-compliant specification. | | **Trust framework** | Defines who counts as accredited, what technical standards apply, what privacy and security obligations attach to accredited parties, and how enforcement works. | The governance layer. Typically established by primary legislation, refined through regulations and standards, and operated by an accreditation authority. | | **Facilitation mechanism** | Holds credentials and presents them. Implements selective disclosure, refuses to "phone home" to its supplier during presentations, supports biometric access on the device, and supports user-initiated revocation. | The citizen-facing software. The wallet. It operates under the citizen's control rather than the issuing authority's. | Outside all four layers sit **relying parties**. These are the businesses, agencies, and organizations that receive credential presentations. They are bound by general data protection law, such as the GDPR in Europe and the Privacy Act in New Zealand and Australia, but they are not, in most jurisdictions, bound by the trust framework itself unless they are also operating an accredited service. Closing this gap is one of the most consistent open policy questions across credential programs. See [Policy considerations](/docs/concepts/policy-considerations#trust-framework-scope-and-the-relying-party-gap) for the design choices it opens up. This four-layer view lets you point precisely at where each policy question lives, rather than collapsing them all into a single "decentralized identity" conversation. Most of the policy work sits at the issuing authority and trust framework layers. The issuance platform and facilitation mechanism are largely technical, operating to standards set by the layers above them. The assurance that flows through all four layers is established before a credential is issued, through identity proofing and holder binding. Because the issuer is out of the transaction loop once the credential is in the wallet, the strength of that upfront step sets the ceiling for everything downstream. See [Identity proofing and holder binding](/docs/concepts/identity-proofing-and-holder-binding). ## Why architectural shifts matter for governance [#why-architectural-shifts-matter-for-governance] Each era's safeguards were designed for that era's architecture. When governance instruments from earlier eras are carried forward unchanged, two failure modes follow. The first is **over-collection by reflex**: treating a decentralized system as if it were federated and requiring providers to log, retain, or report on individual transactions. This recreates the surveillance properties the architecture was specifically designed to eliminate. The second is **under-protection by assumption**: treating decentralization as if it solved every privacy and integrity question on its own. It does not. The new architecture moves the locus of risk away from the identity provider's database and onto the relying party's intake form. If the relying party asks for more than it needs, the holder presents more than necessary, and the data ends up retained in places the architecture cannot reach. The work of policy in era three is to move the safeguards to where the risks actually sit. ## Six shifts in governance focus [#six-shifts-in-governance-focus]
### From gatekeeping access to setting issuance terms [#1-from-gatekeeping-access-to-setting-issuance-terms] In the federated era, the identity provider controlled access transaction by transaction. Every verification request was authorized in real time. Governance worked by sitting in the loop. In the decentralized era, **the issuer is out of the loop by design.** The credential travels independently of the issuer. Governance must move upstream, to the moment of issuance, and downstream, to the conditions under which relying parties may consume credentials. The lever is no longer access control. It is **issuance policy and accreditation**. In practice, this means specifying at issuance time what the issuer expects of any party that subsequently consumes the credential, and ensuring those expectations are technically and legally enforceable.
### From surveillance-based oversight to architectural safeguards [#2-from-surveillance-based-oversight-to-architectural-safeguards] Federated systems supported oversight through logs. The intermediary saw every transaction and could be required to report on patterns, anomalies, and breaches. Oversight was retrospective and pattern-based. Decentralized systems cannot support that model without breaking their core property. Many trust frameworks now explicitly prohibit issuers, wallets, and trust framework providers from tracking or correlating verification activity. Policy makers seeking equivalent oversight assurance must look to architectural and statistical instruments instead of transactional ones: * **Aggregate issuance metrics** (credentials issued, revocation rates, expiry patterns) give regulators system-level visibility without individual tracking. * **Mandatory incident reporting** from accredited issuers, wallets, and verifiers provides accountability without surveillance. * **Operationally immediate revocation** gives the issuer prospective protective capability that is more powerful than retrospective logging, because it stops harm in flight rather than describing it after the fact.
### From "what data do we hold" to "what claims can be presented" [#3-from-what-data-do-we-hold-to-what-claims-can-be-presented] The federated era was preoccupied with the security of held data: breach response, encryption at rest, access controls. These remain important, but they answer a question that matters less in era three. The decentralized era's central question is **what claims can be presented and verified without underlying data ever transferring.** "Over 18: yes" is a claim. "Citizen: yes" is a claim. Neither requires a date of birth or a passport number to leave the wallet. Selective disclosure and zero-knowledge proofs are the technical instruments. Policy frames whether they are mandated, encouraged, or optional. A reasonable default is that for any high-value credential issued by a government or regulated body, selective disclosure should be the default presentation mode, and full-record disclosure should require a specific justification by the relying party.
### From purpose limitation as compliance to purpose binding as architecture [#4-from-purpose-limitation-as-compliance-to-purpose-binding-as-architecture] Purpose limitation has long been a principle of privacy law: information collected for one purpose should not be used for another. Historically this has been auditable but not architecturally enforced. In era three, purpose binding can be embedded in the credential itself. An issuer can specify that a particular attribute is presentable only for age verification, or only to accredited services, or only for a specific declared purpose. OID4VP requires the relying party to declare its purpose machine-readably before the credential is presented. The wallet enforces the issuer's policy. The user sees the purpose. The architecture does the work that compliance auditing previously did after the fact.
### From relying-party trust by reputation to relying-party trust by accreditation [#5-from-relying-party-trust-by-reputation-to-relying-party-trust-by-accreditation] In the federated era, the identity provider implicitly extended trust to whichever relying parties it chose to serve. The relying party's reputation, contractual position, or sector regulation did the work of justifying that trust. In era three, **accreditation under a trust framework** is the formal instrument. For credentials carrying high-assurance attributes (nationality, citizenship, regulated qualifications), consumption can be restricted to accredited services. Lower-assurance attributes can be accessible to parties that have signed binding issuer terms without full accreditation. Trust frameworks and trust registries are the enforcement mechanism. They let the ecosystem know which issuers, wallets, and verifiers meet defined obligations without putting the issuer in the transaction loop. See [Verifiable credential ecosystem](/docs/concepts/credential-ecosystem) for more on these roles.
### From data linkage as capability to data linkage as risk [#6-from-data-linkage-as-capability-to-data-linkage-as-risk] Federated systems often combined data across agencies or institutions to enrich identity assertions. The capability was valued; the risk was managed contractually. In era three, cross-organization data combination becomes the principal residual risk inside large institutional ecosystems, even as the risks outside them recede. Combining citizenship data with driver licensing data with business registration data to construct profiles no single dataset was collected to produce is the surveillance-by-linkage scenario decentralized architectures were specifically designed to prevent, and which institutions should not replicate internally. A useful discipline is **verification without retention**: where an issuer needs to confirm a fact against another authoritative source, the claim should be verified at issuance time and the source data discarded. Verification happens, the credential is issued, the third-party data is not retained.
## Common pitfalls when adopting a decentralized model [#common-pitfalls-when-adopting-a-decentralized-model] Practitioners new to the model often carry forward instincts that no longer serve. Among the most common: * **Logging all verification events centrally**, because that is how oversight worked before. This breaks the architecture's privacy guarantees and is often prohibited by the trust framework rules that govern it. * **Requiring every credential to be checked against a live issuer endpoint at presentation time.** This re-creates the federated dependency the model was designed to eliminate. Use offline-verifiable signatures and published status lists instead. * **Asking for full credentials when only a single attribute is needed.** This pushes the data-minimization principle outside the architecture's enforcement zone. Use selective disclosure by default. * **Treating the wallet as a passive container.** In era three, the wallet enforces issuer policy, mediates user consent, and represents the holder. It is a governance-bearing component, not just a user interface. * **Assuming the technology alone delivers privacy.** It does not. The architecture removes specific risks (issuer-side surveillance, central correlation) but does not address what relying parties do with the data once they receive it. That work happens in issuance policy, accreditation, and trust framework rules. ## What this means for policy makers and stakeholders [#what-this-means-for-policy-makers-and-stakeholders] Decentralized trust does not reduce the need for governance. It changes where governance is applied and what instruments are available. The strongest protections in a decentralized ecosystem come from designing the system so that misuse is **technically constrained**, not from adding compliance obligations on top of an architecture that already permits the harm. Where policy work is grounded in the architecture, the resulting safeguards are durable, technically enforceable, and proportionate. Where it is not, safeguards either become compliance overhead that the architecture has already made redundant, or they reintroduce surveillance properties the architecture was specifically designed to remove. For policy makers, governance bodies, and ecosystem stakeholders, the recommendation is to **use the architecture**. Decide what claims may be issued, by whom, to whom, and under what conditions. Specify selective disclosure as the default. Treat trust frameworks and accreditation as primary instruments. Move oversight from transactional logging to aggregate metrics and incident reporting. Recognize that the harder work is not the technology. It is building the institutional capability to operate the ecosystem well: revocation operations, accreditation compliance, relying-party oversight, fraud response, and equity and access management. The technology is ready. The standards are in place. The remaining work is governance, and it is the right hard work to do. ## Frequently asked questions [#frequently-asked-questions] ### What is a decentralized trust model? [#what-is-a-decentralized-trust-model] A decentralized trust model is an approach to digital identity in which an authoritative issuer issues a verifiable credential to a holder's wallet, and the holder presents that credential directly to relying parties without an intermediary in the transaction loop. The credential is cryptographically signed by the issuer so the relying party can verify it independently, and no central party sees where the credential is being used. ### How is decentralized trust different from federated identity? [#how-is-decentralized-trust-different-from-federated-identity] In a federated model, an identity provider authenticates the user and asserts their identity to relying parties, so the intermediary sees every transaction. In a decentralized model, the issuer is out of the loop once the credential is issued. The relying party verifies the credential cryptographically without contacting the issuer, and no party holds a complete record of how a person uses their identity. ### What technical standards does decentralized trust rely on? [#what-technical-standards-does-decentralized-trust-rely-on] The decentralized trust model rests on a stack of open international standards. These include the W3C Verifiable Credentials Data Model 2.0 for credential structure, ISO/IEC 18013-5 for mobile driving licenses, SD-JWT VC for selective disclosure, OpenID for Verifiable Presentations (OID4VP) for presentation requests, OpenID for Verifiable Credential Issuance (OID4VCI) for issuance, and the W3C Digital Credentials API for browser integration. ### What are the four layers of a credential ecosystem? [#what-are-the-four-layers-of-a-credential-ecosystem] A credential ecosystem can be read in four functional layers. The issuing authority decides what may be asserted and holds the revocation mandate. The issuance platform turns those instructions into signed credentials. The trust framework defines accreditation, technical standards, and obligations. The facilitation mechanism, or wallet, holds and presents credentials under the citizen's control. Relying parties sit outside these four layers and are bound by general data protection law. ### Does decentralized trust eliminate the need for governance? [#does-decentralized-trust-eliminate-the-need-for-governance] No. Decentralized trust does not reduce the need for governance. It changes where governance is applied and what instruments are available. Architectural safeguards do much of the work that contractual rules did in earlier models, but policy is still needed for issuance terms, accreditation, relying party obligations, equity, and enforcement. ### Can a decentralized credential be used offline? [#can-a-decentralized-credential-be-used-offline] Yes. Because the credential carries a cryptographic signature, a verifier can confirm authenticity offline without contacting the issuer for each transaction. Status checks for revocation may require online connectivity depending on the design, but the core verification of issuer authenticity and credential integrity can be performed locally. # Decentralized Identifiers (DIDs) URL: /docs/concepts/dids ## Overview [#overview] Decentralized identifiers [(DIDs)](https://www.w3.org/TR/did-core/) are globally unique, highly available and cryptographically verifiable digital identifiers. They are typically represented as a Unique Resource Identifier (URI) that can point to a person, organization, data model or any abstract entity. The main difference between DIDs and traditional identifiers (e.g. email address or user account) is that they are not owned by any service provider or organization. As such, they can be used across platforms and prevent vendor lock-in. DIDs are a [W3C standard](https://www.w3.org/TR/did-core/) and can be extremely effective at preserving user privacy, enhancing transparency and consent, enabling data portability and enforcing user control. DIDs can be used in identity management systems and provide superior security and encryption compared to passwords by using public/private key pairs instead. Thus, DIDs offer a different trust model to centralized identifiers. Specifically, DIDs form the basis of a [Decentralized Public Key Infrastructure (DPKI)](https://github.com/WebOfTrustInfo/rwot1-sf/blob/master/draft-documents/Decentralized-Public-Key-Infrastructure-CURRENT.md) for the web. DIDs are classified according to their [DID method](https://www.w3.org/TR/did-core/#dfn-did-methods). Each method defines a CRUD model to describe how a specific DID scheme works with a specific [verifiable data registry](https://www.w3.org/TR/did-core/#dfn-verifiable-data-registry) such as a distributed ledger or blockchain. There are many [dozens of DID methods](https://w3c.github.io/did-extensions/methods/#did-methods) that defined their own specifications and contributed their DID scheme to the W3C. DIDs are used through a process known as [DID resolution](https://www.w3.org/TR/did-core/#resolution), which locates the registry where the DID is anchored (based on its DID method) and retrieves its corresponding [DID document](https://www.w3.org/TR/did-core/#dfn-did-documents). This is a JSON document that contains cryptographic material such as public keys as well as ways to interact with the DID subject via service endpoints. ## Structure [#structure] DIDs are Unique Resource Identifiers (URIs) that follow the following pattern: Did Structure **`did:key` example** ``` did:key:z6MkjBWPPa1njEKygyr3LR3pRKkqv714vyTkfnUdP6ToFSH5 ``` ## Methods [#methods] Different DID methods have different properties and are better suited to some use cases over others. There isn't a DID method that should be universally applied to every situation so getting a good understanding of the different offerings will help you decide which is going to work best for your use cases. MATTR VII currently supports the following DID methods: * `did:key` : The most basic type of DID. The public key forms the DID and has no further data associated with it. **Example**: `did:key:z6MkjBWPPa1njEKygyr3LR3pRKkqv714vyTkfnUdP6ToFSH5` * `did:web` : This type of DID hosts requires hosting the DID document on a publicly accessible domain in order to make the document and contents available. **Example**: `did:web:learn.vii.au01.mattr.global` ### `did:key` [#didkey] DIDs with a method of `key` are the most basic type of DID. The public key forms the DID and has no further data associated with it. MATTR VII automatically registers created DIDs when applicable. Any `did:key` will always have a `registrationStatus` of `COMPLETED` as it is instantly available to be used and resolved. #### Constraints [#constraints] * This DID method does not support key rotation, and therefore there are limitations on using it in a long-term setting. When keys need to be rotated for any reason, all credentials issued using this DID must be revoked and reissued using the new key. * This DID method does not advertise a service endpoint, where a DID document advertises a public endpoint that could be used to send messages intended for the DID owner. As this DID method does not support this capability, any messages will need to be routed by different means. MATTR VII capabilities enable registering a `did:key` with a static inbox, so that all messages intended for the DID owner are routed to that mailbox. #### Key types [#key-types] * Supported `keyType` for `did:key` are `Ed25519` and `Bls12381G2`. * If the `keyType` is omitted, the default `keyType` is `Ed25519`. This `keyType` can be used as a Verifier DID. * If the `keyType` is set to `Bls12381G2` the created DID supports BBS+ signatures for creating ZKP and selective disclosure enabled credentials. * As `did:key` only support `Ed25519` or `Bls12381G2` key types, you can not use it to create CWT credentials. If you wish to create CWT credentials, create a DID using a keyType of `P-256`, such as `did:web`. * `Bls12381G2` cannot be used as a Verifier DID as it does not support symmetric key signing required to verify messages. ### `did:web` [#didweb] DIDs using the `web` method host the DID document on a publicly accessible domain in order to make the document and contents available. For example, MATTR has a `did:web` using our own domain `did:web:mattr.global`. This DID is hosted on our website at `https://mattr.global/.well-known/did.json`. DIDs may also be hosted at specific paths on your website. #### Constraints [#constraints-1] * `did:web`s inherently rely on the security of the website the DID Document is hosted on. We would only recommend the use of this type of DID on trusted and known sites/domains, such as government agencies and enterprises. * Message signing is not possible at this time using DIDs that only contain a `bls12381g2` key type. Note that MATTR VII creates `did:web`s with multiple key types by default to overcome this constraint. * When you create a new [credential configuration](/docs/issuance/credential-configuration/overview) and don't specify an explicit issuer DID, the most recently created `did:web` on your tenant is used as an issuer DID by default. #### Key types [#key-types-1] MATTR VII creates all `did:web` with multiple key types by default. This enables issuers to issue different types of credentials from a single DID, making use of the different key types features: * `P-256` : This is the default option for signing [CWT credentials](/docs/concepts/cwt). * `Bls12381G2Key2020` : * Supports ZKP-enabled credentials. * Supports key rotation. * `X25519` suite: * `Ed25519` : Recommended when the `web:did` is used as a Verifier DID, as it supports symmetric key signing required for verifying messages. #### Hosting [#hosting] To make use of your `did:web`, you must make its DID document publicly available. It is important to understand that verifiers will rely on accessing the DID document to verify credentials issued using your did:web. Therefore, the did.json file needs to be highly available. DID documents are not expected to change very often, so caching is encouraged using standard caching headers. A global CDN is also highly recommended to reduce latency during verification, as wallets may make multiple calls from across the world (use-case dependent). Furthermore, if the DID is tied to an issuing infrastructure, when the latter is taken down for maintenance it will prevent both issuing and verifying during that downtime. Taking these considerations into account, you can either host your did:web on your MATTR VII tenant, or on your own domain. #### Resolving [#resolving] MATTR VII can resolve hosted `did:web`s by retrieving their hosted DID document. The tenant can prove ownership of the keys associated with the DID Document through the `https://{your_tenant_url}/.well-known/did-configuration` endpoint. The fact that the DID Document is hosted on the domain links it to the DID. ## DID document [#did-document] Every DID can be resolved to a DID document. A fully resolved DID document typically includes information such as public keys, service endpoints and authentication mechanisms. It can include multiple public keys of different types to support various cryptographic algorithms or use cases ```json title=DID document { "id": "did:web:learn.vii.au01.mattr.global", "@context": [ "https://w3.org/ns/did/v1", "https://w3id.org/security/suites/x25519-2019/v1", "https://w3id.org/security/suites/jws-2020/v1", "https://w3id.org/security/suites/ed25519-2018/v1", "https://w3id.org/security/bbs/v1" ], "keyAgreement": [ { "id": "did:web:learn.vii.au01.mattr.global#FZXtUmNERo", "type": "X25519KeyAgreementKey2019", "controller": "did:web:learn.vii.au01.mattr.global", "publicKeyBase58": "FZXtUmNERow4qGYb4LnLAVETodg7j7LrGyR78keGemWk" } ], "authentication": ["did:web:learn.vii.au01.mattr.global#Fo5mW6ivUV"], "assertionMethod": [ "did:web:learn.vii.au01.mattr.global#z12L6Q6v", "did:web:learn.vii.au01.mattr.global#Fo5mW6ivUV", "did:web:learn.vii.au01.mattr.global#24QqAnwtn4" ], "verificationMethod": [ { "id": "did:web:learn.vii.au01.mattr.global#z12L6Q6v", "type": "JsonWebKey2020", "controller": "did:web:learn.vii.au01.mattr.global", "publicKeyJwk": { "x": "hLdNSMnIaT1h-fjft9zX-nd_khjG7LERGImyNhzAlqU", "y": "ofu7wybOMA8ltzS3gYgdr0DMhlmJNmjhdYpwcHT1_O8", "crv": "P-256", "kty": "EC" } }, { "id": "did:web:learn.vii.au01.mattr.global#Fo5mW6ivUV", "type": "Ed25519VerificationKey2018", "controller": "did:web:learn.vii.au01.mattr.global", "publicKeyBase58": "Fo5mW6ivUVsVS8e6EWuTFguQxTGFDMAZ7JBLuVqPsf1e" }, { "id": "did:web:learn.vii.au01.mattr.global#24QqAnwtn4", "type": "Bls12381G2Key2020", "controller": "did:web:learn.vii.au01.mattr.global", "publicKeyBase58": "24QqAnwtn4AkCwiJHyBBe1eiyo6UegLx6AK8c3XSDUnopaNJP3EZvzgn8fLTaSWFY9T59PxdMp5RqoBBpCg8nGUdJnTVcTX7BPs8ubu516CNxNUehzwyTYzNdRt6YVqQSRMw" } ], "capabilityDelegation": [ "did:web:learn.vii.au01.mattr.global#z12L6Q6v", "did:web:learn.vii.au01.mattr.global#Fo5mW6ivUV", "did:web:learn.vii.au01.mattr.global#24QqAnwtn4" ], "capabilityInvocation": [ "did:web:learn.vii.au01.mattr.global#z12L6Q6v", "did:web:learn.vii.au01.mattr.global#Fo5mW6ivUV", "did:web:learn.vii.au01.mattr.global#24QqAnwtn4" ] } ``` ## Verification relationships [#verification-relationships] Verification relationships are the building blocks in a DID document. They determine the relationship between a key and the different capabilities it can be used for. DID documents include the following verification relationships: * `assertionMethod` * `authentication` * `keyAgreement` For example, when a credential proof is created, the used private key will be linked to the corresponding public key as referenced in the assertionMethod section. By resolving the DID document for the Issuer DID it’s possible for anyone to validate the credential proof, verifying that the Issuer has asserted the data in the credential to be true. During a verification flow, these checks are handled automatically using secure industry-leading methods, providing a simple result. ## DID URL [#did-url] When referencing a specific sub-resource inside a DID document, we use the common [fragment](https://www.w3.org/TR/did-core/#fragment) URLs pattern. When a DID key is specified in a single string, this is usually known as a DID URL, as shown in the following example: ``` did:key:z6MkjBWPPa1njEKygyr3LR3pRKkqv714vyTkfnUdP6ToFSH5#z6Mkn9kQbVXdeDW3h3GvYUV5BzTQDw5oh26CGDqS7sZq3kBN ``` Strictly speaking, any DID is a URL. However, we generally have used the term DID URL to mean the more specific version, so in our [API Reference](/docs/api-reference), `didUrl` would mean providing a value of the DID with the # to reference an exact key. ## Usage [#usage] MATTR VII tenants support managing DIDs, which includes creating, retrieving and deleting DIDs. When creating a new DID, MATTR VII generates the required keys and metadata and registers the DID on a public ledger (if applicable). When retrieving DIDs, the tenant resolves them to get the latest key and service information. MATTR VII establishes a verifiable relationship between your tenant domain and DIDs created by your tenant, enabling linkage between an internet domain owner and a DID owner. This approach creates a bridge that connects the traditional trust model of the internet with a distributed trust model. This follows the [Well Known DID Configuration](https://identity.foundation/.well-known/resources/did-configuration/) open standard developed by the [Decentralized Identity Foundation (DIF)](https://identity.foundation/). ## Common DID usages in MATTR VII [#common-did-usages-in-mattr-vii] ### Issuer DID [#issuer-did] MATTR VII can be used by issuers to create DIDs that are used in CWT and Semantic CWT credential proofs. These are referred to as Issuer DIDs for these credential formats. The [DID document](#did-document) must be publicly discoverable by verifiers so they can know that the DID keys used for the credential proof are from an issuer they trust. This could be via a government backed or large enterprise ecosystem, or another organization they already know and trust. Issuer DIDs private keys are stored in the MATTR VII Key Management System (KMS), and published on the [DID Configuration endpoint](/docs/api-reference/platform/dids/wellKnownDidConfig). They can be used to create different types of credentials, as well as support revocable credentials. ### Verifier DID [#verifier-did] MATTR VII can be used by verifiers to create DIDs that are used in a credential presentation exchange. These are referred to as Verifier DIDs. The credential holder needs to be sure that the received presentation request is from a valid verifier before sending their personal credential data in the response. Even if other forms of trust are in place (e.g. the journey started from a website that the holder trusts) the DID to domain linkage must still be verified to validate the domain against the values used in the request. Verifier DIDs private keys are stored in the MATTR VII KMS, and published on the [DID Configuration endpoint](/docs/api-reference/platform/dids/wellKnownDidConfig). They must support message signing or key agreement for encryption. ### MATTR wallets [#mattr-wallets] When someone uses their MATTR wallet app to interact with a new domain (either issuer or verifier), a unique DID is created for that interaction. #### Subject DID [#subject-did] Verifiable credentials that are linked to a specific holder (e.g. an education certificate) are referred to as subject-bound credentials. When a DID from a wallet user is used in subject-bound credentials, it is referred to as a *Subject DID*. #### Holder DID [#holder-did] When a DID is used to create presentations in response to verifier requests, it is referred to as a *Holder DID*. MATTR VII generates a unique and new Holder DID for each issuer/verifier interaction. If the verifier and the issuer are hosted on the same domain and using the same MATTR VII tenant, the same DID will be used for both the Holder DID and the Subject DID. As part of the [W3C Verifiable Credential data model specification](https://www.w3.org/TR/vc-data-model/#presentations) the verifiable presentation model is used to wrap all credentials inside a presentation and apply cryptographic proofs. The *Subject DID* from each credential is used to sign the presentation, as well as the *Holder DID* (even if they are the same). The *Holder DID* is the only DID presented to the verifier during DID auth presentation requests. #### Public DID [#public-did] DIDs enable sending messages and data directly to the DID owner, assuming the sender has a way of retrieving the intended recipient DID. One such example is a Public DID that can be used to send secure messages and issue credentials to a specific digital wallet that is linked to this DID. # Credential formats URL: /docs/concepts/formats-overview MATTR credential formats combine and evolve with the latest standards and technology stacks to make working with verifiable credentials seamless. Selecting the right format for a solution depends on a number of factors, including but not limited to: * The size of the payload. * The need for biometric or other identity assurance capabilities. * The cryptographic scheme used. * The need for privacy-preserving features like selective disclosure. * The primary method of issuance, presentation and verification. [Contact us](mailto:dev-support@mattr.global) if you need help deciding what credential format is right for your use case. MATTR platforms currently support the following credential formats: * [mDocs](#mdocs) * [CWT credentials](#cwt-credentials) ## mDocs [#mdocs] mDocs are digital credentials based on the ISO/IEC [18013-5](https://www.iso.org/standard/69084.html) standard and [18013-7](https://www.iso.org/standard/91154.html) technical specification, designed to be stored on a holder’s mobile device. mDocs support a variety of advanced security features, making them an ideal choice for use cases requiring higher assurance identity credentials, such as driving licenses or national IDs. mDocs verification workflows can be carried out over non-internet communication protocols such as BLE (facilitating in-person exchange and offline verification), or via online presentation channels (facilitating online presentation flows). Both these workflows support selective disclosure and enable authenticating the issuer, the holder, and the device the mDocs are presented from. Key architectures and technology stacks for mDocs: * Based on the [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) standard and the [18013-7](https://www.iso.org/standard/91154.html) technical specification. * Use CBOR for object representation and COSE for signing and encryption. * Use X.509 certificates for implementing chain of trust authentication workflows. * ECDSA with P-256 (ES256) algorithm support for issuer and device authentication. Learn more about [mDocs](/docs/concepts/mdocs). ## CWT credentials [#cwt-credentials] CWT credentials are best-suited for sharing authentic information simply. They carry a smaller payload and are optimized for presenting in-person, whether affixed to a physical document or item or displayed digitally on-screen. Digital signatures ensure the authenticity of information included within the credential, however, do not include assurance about the person presenting the information. If identity assurance is needed, this can be done through attribute matching with an outside identity document. Key architectures and technology stacks for CWT credentials: * CBOR Web Token (CWT) data model. * W3C Verifiable Credential (VC) JSON data model. * NIST-approved `P-256` key types. * COSE digital signature encoding. Learn more about [CWT credentials](/docs/concepts/cwt). ## JSON credentials [#json-credentials] JSON Credentials are based on the [W3C Verifiable Credential (VC) data model](https://www.w3.org/TR/vc-data-model/) for expressing cryptographically secure digital credentials on the web. VC provide a data sharing model where signed and linked data can be used to establish trust across contexts. # Glossary URL: /docs/concepts/glossary This glossary is a collection of terms and definitions related to digital credentials, decentralized identity, and the technologies and standards that support them. It is intended to help you understand the concepts and terminology used in the MATTR ecosystem and the broader field of digital credentials. ## A [#a] **Assurance Level:** A measure of confidence in the authenticity of an identity or credential (often called *Level of Assurance*, or *LoA*). Higher assurance levels indicate stricter verification and greater trust in a credential’s validity, suitable for sensitive uses (e.g. a digital passport), while lower assurance levels are acceptable for less sensitive uses (e.g. a loyalty card). ## B [#b] **Bearer Credential:** A type of credential that grants access or rights to whoever possesses it, without requiring proof of the holder’s identity. Similar to how cash works — anyone who has it can spend it. In the digital world, bearer credentials are often easier to use but come with higher risks if lost or stolen. MATTR's [CWT (CBOR Web Token) credentials](/docs/concepts/cwt) are an example of bearer credentials; they can be presented and verified without requiring the verifier to confirm the presenter’s identity, which is useful for certain offline or privacy-focused use cases. ## C [#c] **Claim:** A piece of data or attribute about a subject (such as a name, age, or qualification) that is asserted by the issuer in a credential. Multiple claims make up the content of a digital credential, and each claim is attested to (signed) by the issuer. For example, a university degree credential might contain claims about a person’s name, the degree earned, and the graduation date. **Certificate Authority (CA)**: A trusted entity responsible for issuing, signing, and managing digital certificates that bind cryptographic keys to identities (such as individuals, organizations, or services). In the context of digital identity ecosystems (such as verifiable credentials), a Root CA is the top-level authority in a certificate hierarchy. It signs the certificates of subordinate CAs and acts as the ultimate trust anchor. All certificates and digital signatures validated in the system must ultimately trace their trust back to the Root CA, which must be highly secure and carefully governed to maintain ecosystem-wide integrity. **Credential Issuance:** The process of creating a digital credential and delivering it to a holder. During issuance, an *issuer* gathers the relevant information (claims) about the subject, formats it into a credential, and cryptographically signs it. This digital signature ensures the credential’s authenticity and integrity, allowing verifiers to confirm the credential without needing to contact the issuer again. **Credential Lifecycle:** The [sequence of stages](/docs/concepts/credential-ecosystem#credential-lifecycle) that a digital credential goes through from creation to retirement. This typically includes *issuance* (credential creation and signing by an issuer), storage and management of the credential by the *holder*, presentation of the credential to *verifiers*, verification of its validity, and eventual expiration or *revocation* if the credential becomes invalid. Understanding the lifecycle in terms of these roles and steps is key to implementing secure credential solutions. **Credential Offer:** An invitation from an issuer to a holder to receive a credential, typically used in an interactive issuance flow. In this approach (for example, see MATTR's implementation of the [OID4VCI](/docs/issuance/oid4vci-overview) workflow), the issuer provides an offer URL or token describing the credential. The holder reviews the offer and, if they accept it, the credential is then issued to their wallet. **Credential Template (Schema):** A defined structure or blueprint for a credential that an issuer sets up in advance. The template specifies what data fields (claims) the credential will include, the format or schema for those fields, any rules such as validity period, and the cryptographic details (e.g. which keys or signature algorithm to use). By using a template, issuers ensure that all credentials of a certain type follow a consistent structure and meet the required standards. MATTR VII implements credential templates using the [Credential configuration](/docs/issuance/credential-configuration/overview) functionality. ## D [#d] **Decentralized Identifier (DID):** A globally unique, persistent digital identifier that is not tied to any central authority. A DID is typically represented as a URI string (for example, `did:example:123456...`) and can refer to a person, organization, or thing. The main difference from traditional IDs (like usernames or email addresses) is that DIDs are controlled by the user (through cryptographic keys) and are not issued or owned by any single provider, making them portable across different systems and preventing vendor lock-in. Each DID is associated with a document that contains the public keys and other metadata needed to use the DID. [DIDs](/docs/concepts/dids) are used in MATTR products as part of the [CWT](/docs/concepts/cwt) credential format. **Decentralized Identity:** A paradigm for identity management in which individuals and organizations can interact and share credentials without relying on a single centralized authority. In a decentralized identity model, people have control over their own data and digital credentials, typically stored in personal digital wallets, and they decide what information to share and with whom. This approach enhances privacy for users (they only share necessary information) and can offer organizations benefits in security and compliance, since trust is established through cryptography and distributed networks rather than one central database. **Digital Credential (Verifiable Credential):** A digital certificate or token that contains a set of claims about a subject, digitally signed by an issuer so that it can be independently verified. It is the electronic equivalent of a physical credential (like an ID card, license, or diploma), designed to be tamper-evident and trustable. Because of the issuer’s cryptographic signature, anyone (or any system) can cryptographically check that the credential was indeed issued by a legitimate source and that its contents haven’t been altered. **Digital Credentials API (DC API):** A standardized interface designed to streamline the process of presenting and verifying digital credentials to web applications. The DC API is part of the [W3C’s Decentralized Identity Working Group](https://www.w3.org/groups/wg/did/) and is intended to facilitate the integration of digital credentials into web-based applications. It provides a set of APIs and protocols that enable developers to easily implement credential presentation and verification functionalities in their applications. **Digital Identity:** The overall representation of a person, organization, or thing in the digital world, made up of the attributes, identifiers, and relationships that distinguish them across the systems they interact with. Digital identity is broader than any single credential. A credential is one signed assertion of specific claims at a point in time, whereas a digital identity is the wider, evolving collection of information and trust relationships that those credentials, identifiers, and accounts contribute to. In a federated identity model, a subject’s digital identity is established and recognized across multiple independent parties through agreed-upon trust relationships, so that an identity managed by one provider can be relied upon by others without each party needing to hold all of the underlying data. This federated approach contrasts with treating identity as a single document or credential, and instead frames it as something asserted, verified, and reused across a network of trusted participants. In decentralized models, the same broad notion of digital identity applies, but control shifts toward the individual (see *Decentralized Identity* and *Self-Sovereign Identity*). **Digital Trust:** The confidence that digital interactions and transactions are secure, authentic, and reliable. When digital trust is high, users and organizations believe that a person or credential is who/what it claims to be and that data hasn’t been tampered with. Establishing digital trust often involves technologies and frameworks (like verifiable credentials, digital signatures, and trust frameworks) that ensure data integrity, privacy, and the legitimacy of all parties involved in an online exchange. **Digital Trust Service (DTS):** A service that acts as a neutral intermediary to establish trust within a digital ecosystem. Instead of every participant needing to trust each other directly, all participants trust the [Digital Trust Service](/docs/digital-trust-service), which in turn vouches for the identity and integrity of participants and their credentials. This *proxy trust* model simplifies interactions in large networks: the network operator maintains the trust relationships, and each member just trusts the DTS to validate others. By offloading trust management to a DTS, organizations can scale up federations of issuers and verifiers more easily while maintaining a high level of assurance. MATTR solutions can include [Digital Trust Service](/docs/digital-trust-service) capabilities to help organizations establish and manage trust in their ecosystems. **Digital Wallet:** An application (typically on a smartphone or computer) that securely stores digital credentials and the user’s cryptographic keys. A digital wallet allows a holder to organize their credentials and control their use — for example, selecting and *presenting* a credential to a verifier when required. It is analogous to a physical wallet but for digital identity documents: the wallet safeguards credentials, often protecting them with passwords or biometrics, and enables the user to share verified information with others in a privacy-preserving way. [MATTR Pi SDKs](/docs/holding/sdk-overview) enable organizations build their own digital wallets, while the [MATTR GO Hold](/docs/holding/go-hold/getting-started) white-label wallet solution enables getting up and running quickly with limited development effort. **Document Signer Certificate (DSC):** A specific end-entity X.509 certificate used to digitally sign Mobile Security Objects (MSOs) within mobile documents (mDocs), as specified in the ISO/IEC 18013-5:2021 standard. Issued and signed by an Issuing Authority Certificate Authority (IACA), the DSC ensures the integrity and authenticity of the mDoc's data. Each DSC includes a public key, validity period, and signature from the IACA. When an mDoc is presented for verification, the DSC's public key is used to validate the MSO's signature, confirming that the document has not been tampered with and originates from a trusted source. Refer to the [chain of trust](/docs/issuance/certificates/overview#document-signer-certificate-dsc) page for more information. ## H [#h] **Hardware Security Module (HSM):** A physical computing device that securely generates, stores, and manages cryptographic keys. It is used to perform sensitive operations such as digital signing, encryption, and key protection in a tamper-resistant environment — ensuring high levels of security for identity and credential systems. Internal HSMs are built directly into a device (such as a mobile phone or secure element). These HSMs manage keys locally and are tightly coupled with the hardware environment they protect. For example, a mobile phone using an internal HSM might store credentials and perform cryptographic operations without the keys ever leaving the device. On the other hand, external HSMs operate outside of the main system (e.g., in a dedicated on-premise appliance or cloud service) and is accessed over a secure channel. External HSMs are often used by credential issuers, Certificate Authorities (CAs), or trust service providers to securely manage high-value signing keys in centralized environments. **Holder:** The person or entity in possession of a digital credential. The holder typically stores credentials in their digital wallet and is responsible for deciding when and with whom to share them. This role gives the individual control over their own digital identity information - the holder can choose to present a credential (or only part of it, via selective disclosure) to prove something about themselves, while keeping the credential secure and private when not in use. ## I [#i] **Identity Assurance:** The level of confidence that an individual, organization, or object is who or what they claim to be. This is established through processes like identity verification (e.g., checking passports, biometrics, or trusted data sources) during credential issuance. The higher the assurance level, the more rigorous the checks to verify that the subject's identity is accurate and trustworthy. **Information Assurance:** In credential verification, information assurance means ensuring the integrity and validity of the credential’s contents. This includes verifying that the credential’s digital signature is valid (proving the data hasn’t been tampered with) and that the credential meets expected format or schema requirements. It also involves checking that the credential is currently valid - for instance, confirming it hasn’t expired and hasn’t been revoked by the issuer. Together, these checks give the verifier confidence that the information presented is trustworthy and up-to-date. **ISO/IEC 18013-5:** An international standard defining how a mobile device presents a Mobile Driver’s License (mDL) to a verifier in a close proximity (offline) scenario. It specifies the data structure of the mDL and outlines key security features, allowing a verifier to validate the authenticity and integrity of the license when shared from a digital wallet. **ISO/IEC 18013-7:** An international technical specification that extends the mDL ecosystem to support remote (online) presentation of an mDL. It defines protocols for verifying mDLs using a web browser, including RESTful APIs and support for OpenID for Verifiable Presentations (OID4VP) and the Digital Credentials (DC) API. **ISO/IEC TS 23220 (series):** A developing series of international technical specifications designed to broaden the use of secure mobile credentials beyond driver’s licenses. Referred to as Mobile Documents (mDocs), the standards provide a framework for presenting and verifying various types of digital credentials. ISO/IEC TS 23220-4 specifically builds on ISO/IEC 18013-5 and ISO/IEC 18013-7, adding features like holder authentication for a wider range of use cases. **Issuer:** An organization or entity that creates and vouches for a credential. The issuer gathers the relevant data about a subject, encodes it into a credential (following a template or schema), and cryptographically signs the credential with its private key to ensure authenticity. Once issued, the credential is delivered to the holder (for example, added to the holder’s wallet). Anyone who later verifies the credential can check the issuer’s digital signature to confirm the credential came from a trusted source and hasn’t been altered. **Issuing Authority Certificate Authority (IACA):** A self-signed X.509 certificate that serves as the root certificate in the chain of trust for mobile documents (mDocs), such as Mobile Driver's Licenses (mDLs), as defined by the ISO/IEC 18013-5:2021 standard. The IACA identifies the mDoc issuer and is used to sign subordinate certificates, specifically the Document Signer Certificates (DSCs). These DSCs, in turn, sign the Mobile Security Objects (MSOs) within mDocs, ensuring the integrity and authenticity of the credential's data. The IACA's self-signed nature means it is the trust anchor; verifiers rely on its public key to validate the entire certificate chain down to the presented mDoc. IACAs can have validity periods of up to 20 years, facilitating long-term trust in the issued credentials. Refer to the [chain of trust](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) page for more information. ## M [#m] **mDL (Mobile Driver’s License):** A digital version of a driver’s license, designed to be stored on a mobile device like a smartphone. An mDL contains the same personal information and driving privileges as a physical license, but can be presented electronically. Modern mDL implementations use encryption and device security (such as biometric locks and anti-cloning measures) to provide a more secure, fraud-resistant alternative to a plastic ID card. They can also support privacy features like selective disclosure (e.g. proving you are over 18 without revealing your exact date of birth). mDLs are based on the ISO/IEC 18013-5:2021 standard, which defines the technical specifications for mobile driver’s licenses and their secure storage and presentation. MATTR has implemented mDLs as part of the [mDocs](/docs/concepts/mdocs) credential format. **Mobile Security Object (MSO):** A structured, signed data object that provides cryptographic integrity for the individual data elements within a mobile document (mDoc), such as an mDL, as defined in the ISO/IEC 18013-5:2021 standard. The MSO is generated by the issuing authority and digitally signed using the private key associated with a Document Signer Certificate (DSC). It contains cryptographic hashes of each data element included in the mDoc (e.g., name, date of birth, photo), ensuring that verifiers can confirm the authenticity of the data and detect any tampering. The MSO also includes metadata such as the document type, version, and validity period, and is a critical component in establishing trust in mDocs without requiring online connectivity. Refer to the [chain of trust](/docs/concepts/chain-of-trust#mobile-security-object-mso) page for more information. **mDocs (Mobile Documents):** A class of digital identity documents which expand the ISO/IEC 18013-5:2021 standard. mDocs are designed to be stored in a digital wallet on a mobile device and can be verified either in-person or remotely. The key strength of mDocs is their ability to provide strong authentication and high security; they are ideal for high-assurance identity credentials like driver’s licenses, passports, or national ID cards. mDocs use a chain of trust (digital certificates) and mechanisms like device binding to protect against forgery, cloning, and impersonation, making them very robust for digital trust use cases. MATTR has implemented a corresponding [mDocs](/docs/concepts/mdocs) credential format. ## O [#o] **OpenID for Verifiable Credential Issuance (OID4VCI):** An [open standard protocol](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) that adapts OAuth 2.0/OpenID Connect for issuing verifiable credentials. In an OID4VCI flow, an issuer first provides a **credential offer** to the holder (often as a URL or QR code). The holder’s wallet or app then uses an OAuth-like sequence to authenticate (if needed) and retrieve the credential from the issuer’s server. This standard enables interoperability, allowing any compliant wallet to obtain credentials from any compliant issuer in a secure and standardized manner. MATTR has implemented [OID4VCI](/docs/issuance/oid4vci-overview) as part of MATTR VII's issuance capabilities. **OpenID for Verifiable Presentations (OID4VP):** An [open standard protocol](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) that enables the presentation of verifiable credentials using OpenID Connect-based flows. It allows a holder to share credentials with a verifier in a privacy-preserving, secure, and standardized way — often initiated by scanning a QR code or clicking a link. OID4VP helps enable interoperability between different wallets, verifiers, and credential formats, and supports advanced features like selective disclosure and subject binding. MATTR has implemented OID4VP as part of its [mDocs online verification capabilities](/docs/verification/remote-overview). **Original Equipment Manufacturer (OEM):** In this context, refers to a company that integrates MATTR’s solutions into their own hardware or software products. For example, a point of sale device might integrate MATTR’s credential verification capabilities to accept digital IDs or credentials from customers. The OEM uses MATTR’s technology to enhance their product offerings, providing their customers with advanced features like secure credential storage, verification, and presentation without needing to develop these capabilities from scratch. This allows OEMs to leverage MATTR’s expertise in digital identity while focusing on their core business. ## R [#r] **Relying Party:** A term for an entity that depends on an outside credential or identity proof to make decisions - essentially the same as a *verifier* in the context of digital credentials. The relying party “relies” on the credential’s authenticity and integrity. For example, a bank’s website acting as a relying party might accept a digital ID credential to onboard a new customer, trusting that the credential is valid (It relies on the issuer and the credential’s verification checks rather than collecting the data directly). **Revocation:** The process of marking a previously issued credential as no longer valid or trustworthy. An issuer may revoke a credential if, for instance, the credential’s subject no longer meets certain conditions (imagine a professional license that gets revoked) or if the credential was compromised. Revocation is typically implemented by adding the credential’s identifier or status to a *revocation list* or status registry. When a verifier checks a credential, they can look up its status on that list to see if the credential has been revoked, all without revealing which specific credential is being checked (to preserve privacy). MATTR solutions support [revocation](/docs/issuance/revocation/overview) for CWT and mDocs credential formats. ## S [#s] **Selective Disclosure:** A privacy-enhancing feature that allows a credential holder to reveal only the specific pieces of information required by a verifier, rather than sharing the entire credential. For example, with selective disclosure, you could prove you are over 18 by sharing a verified “over-18” attribute from your credential, without disclosing your full name or exact birthdate. This is usually achieved with advanced cryptography so that the verifier can be sure the undisclosed parts of the credential remain hidden and the revealed data is authentic. MATTR solutions support selective disclosure for [mDocs](/docs/concepts/mdocs/core-capabilities#selective-disclosure). **Self-Sovereign Identity (SSI):** An approach to digital identity in which individuals fully own and control their personal identity information and credentials. In an SSI system, users (as holders) receive verifiable credentials from various issuers (e.g. a government ID, a college degree, a workplace ID) and store them in their personal wallet. The user decides which credentials or claims to share with a verifier, without needing any central intermediary in the transaction. No single authority “owns” the identity - it’s decentralized across many issuers and controlled by the individual. This model often leverages DIDs and blockchain or other distributed ledgers to anchor trust, enabling a decentralized yet trustworthy ecosystem for identity. ## T [#t] **Trust Framework:** The set of governance rules, policies, and technical standards that define how participants in a trust network operate and trust each other. A trust framework lays out, for example, which issuers are recognized as authoritative, what credentials formats are acceptable, what security and privacy standards must be met, and how disputes or failures are handled. It provides the legal and procedural foundation for the network, ensuring that all members (issuers, holders, verifiers) have a common understanding of the “rules of trust” they abide by. By adhering to a shared trust framework, different organizations can accept each other’s credentials with confidence. **Trust Network:** A group or ecosystem of organizations and individuals that agree to mutually recognize and accept digital credentials under a common trust framework. Within a trust network, there may be many issuers, holders, and verifiers interacting across various use cases (e.g. a network could include government agencies, banks, universities, and individuals all using the same credential system). Because all participants follow the agreed rules and standards, a credential issued by one entity (say, a government ID) can be trusted and verified by another entity (say, a bank) without custom integration. In essence, the trust network ensures interoperability and trust across organizational boundaries by establishing shared expectations and infrastructure for digital trust. ## U [#u] **Un-managed IACA:** A deployment pattern where the Issuer Authority Certificate Authority (IACA) operates independently without centralized oversight or coordination (such as from a Digital Trust Service). Organizations may choose this model when they want to control their own credential issuance trust anchors, particularly in closed or private ecosystems. While this offers flexibility and autonomy, it requires careful governance to ensure interoperability and trust. ## V [#v] **Verifiable Presentation:** A package of information prepared by a holder that contains one or more credentials (or selected parts of credentials), along with cryptographic proof that the holder possesses those credentials. A verifiable presentation is typically created when a holder needs to prove something to a verifier: the holder’s wallet collects the necessary data from their credentials and signs the presentation to prove its authenticity. The verifier can then check the signature and the included credential proofs to ensure everything is valid and was issued to that holder. Verifiable presentations allow a holder to consolidate claims from different credentials and present them in one go, with assurance that the data is authentic and belongs to the holder. **Verifier:** The entity that requests and validates a credential presented by a holder. A verifier checks that the credential was issued by a trusted issuer and that it hasn’t been tampered with or revoked. This is done by cryptographically validating the issuer’s signature on the credential and consulting any relevant status information (like an expiration date or revocation registry). If the credential passes these checks, the verifier can trust the claims contained in it. In some contexts, a verifier is also known as a *relying party* (because it *relies* on the credential’s authenticity and the issuer’s trustworthiness). **VICAL (Verified Issuer Certificate Authority List):** A consolidated list of trusted issuer certificate authorities, used to simplify trust in ecosystems with many issuers. A VICAL is defined in the ISO/IEC 18013-5 standard as a mechanism where a central authority (often a national Digital Trust Service) collects and cryptographically signs a list of issuer CAs that are considered trustworthy. By publishing this single, signed list, the VICAL makes it easy for verifiers to trust credentials from any issuer on the list without needing separate agreements with each issuer. In practice, if a verifier trusts the VICAL, they will trust any credential whose issuer’s root certificate appears in the VICAL, greatly streamlining verification of credentials from multiple jurisdictions or organizations. MATTR offers [VICAL](/docs/digital-trust-service/vical-overview) solutions as part of its Digital Trust Service capabilities. ## Z [#z] **Zero-Knowledge Proof (ZKP):** A cryptographic protocol that allows one party (the *prover*) to prove to another party (the *verifier*) that a certain statement is true, without revealing any additional information beyond the truth of the statement itself. In the context of digital credentials, ZKPs enable a holder to convince a verifier of something about their data *without* exposing the data. For example, a ZKP can allow you to prove “I am over 18 years old” by using your credential, without ever showing your actual birth date. The verifier ends up convinced of the fact (over-18) but learns nothing else. This technology is key to advanced privacy-preserving interactions in decentralized identity systems. # Identity proofing and holder binding URL: /docs/concepts/identity-proofing-and-holder-binding Description: How an issuing authority establishes confidence that a credential is issued to, and controlled by, the right person, and why that confidence can be established once and reused across the ecosystem. Confidence in an authority-issued credential rests on a question that is easy to state and harder to operationalize: how sure is the issuing authority that a credential belongs to the right person? In a [decentralized trust model](/docs/concepts/decentralized-trust-model), the issuer is out of the transaction loop once a credential is issued, so the assurance that matters most is established before the credential ever reaches a relying party. The strength of that assurance is set during issuance, not at presentation. This page explains the three distinct moments where identity is verified, why holder binding is the most consequential of them, and why confidence established once can be reused across the ecosystem rather than re-litigated at every interaction. ## The three verification moments [#the-three-verification-moments] Confidence in a credential depends on three separate verification moments, each serving a different purpose and each governed differently. | Moment | What happens | Who is responsible | | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | **Identity record creation or update** | Foundational identity proofing. The person is verified against authoritative records, with liveness confirmed where appropriate, establishing the record from which future credentials are derived. | Issuing authority | | **Credential provisioning and holder binding** | The person requesting the credential is confirmed to be the person the identity record belongs to. This is the holder-binding event. | Issuance platform or wallet, on behalf of the issuing authority | | **Credential presentation** | A relying party gains confidence that the person presenting the credential is its legitimate holder, often through device-level biometric access controls. | Wallet provider and relying party | The first moment sets the **assurance ceiling**. The rigor of identity proofing at record creation or update determines the maximum assurance any credential derived from that record can carry. No later step can raise a credential above the assurance of the proofing that underpins it. The second moment connects the credential to a person. A credential is only as trustworthy as the process that binds it to its rightful holder during provisioning. The third moment is where the holder demonstrates control at the point of use. For many transactions, the biometric access controls already built into a modern device, such as facial recognition or fingerprint authentication, provide sufficient assurance that the person presenting the credential is its holder. Higher-risk use cases may justify additional verification, but the need for it should be determined explicitly by the sensitivity of the credential and the nature of the transaction rather than applied uniformly. ## Why holder binding is the pivotal decision [#why-holder-binding-is-the-pivotal-decision] The most consequential identity assurance decision in the credential lifecycle is often not how a credential is presented, but how confidently the issuing authority and its ecosystem partners bind the credential to the correct individual during provisioning. At the point of provisioning, a liveness check that confirms the person is physically present and matches the identity record can significantly strengthen holder binding. This is a one-time event, not ongoing surveillance. The biometric is used solely to confirm the binding and can be discarded once the process is complete. For credentials derived from high-assurance authoritative records, stronger holder-binding measures may be appropriate. These can include document verification, facial matching against authoritative records, and liveness detection. The objective is to ensure that a high-assurance credential is issued only to the person entitled to receive it. ### Where holder binding takes place is a policy choice [#where-holder-binding-takes-place-is-a-policy-choice] The key decision is not *whether* holder binding should occur, but *where*. The matching process can be performed by the issuance platform before the credential is delivered, or by the wallet when the credential is claimed. Both approaches can achieve the same assurance outcome. They differ in: * **Where biometric processing occurs**, on a server controlled by the issuance platform or on the holder's device. * **Who controls the matching logic** and is accountable for its accuracy. * **How responsibilities are allocated** across the issuing authority, issuance platform, and wallet provider. What matters is that the holder-binding event is performed, documented, and governed appropriately. The choice between locations is a governance and privacy-design decision rather than a difference in assurance. ## Biometrics belong to proofing, not to the credential [#biometrics-belong-to-proofing-not-to-the-credential] This framing clarifies the role of biometrics in the ecosystem. Biometrics are most valuable as a proofing and holder-binding mechanism, not as an attribute carried inside the credential. The ecosystem gains assurance from the fact that a biometric check occurred, not from sharing biometric information with relying parties. A biometric template is a digital representation of a person's unique physical characteristics, such as a face or fingerprint, that can be used to recognize them. Where biometric processing is used for identity proofing or holder binding, only the outcome of that process, such as *identity confirmed*, should be reflected in the credential. Raw biometric data, facial images, fingerprints, and other reusable biometric information should not be credentialized. This is consistent with the broader principle that several categories of information warrant exclusion from default credential attributes. See [Policy considerations](/docs/concepts/policy-considerations#what-should-and-should-not-be-credentialized). ## Establish confidence once, reuse it across the ecosystem [#establish-confidence-once-reuse-it-across-the-ecosystem] The policy objective should be to establish confidence once and then allow that confidence to be reused. Where high-quality identity proofing and holder binding have already occurred at issuance, relying parties should not need to repeat identity verification unnecessarily. This reduces friction for users, lowers compliance costs, and limits the collection and storage of personal information across the ecosystem. When the three verification moments work together, they establish that: * the issuing authority knows who the individual is, * the credential has been issued to the correct individual, and * relying parties can trust the resulting credential without conducting their own extensive identity verification. This improves trust while reducing the overall privacy footprint of the ecosystem. It is also why [selective disclosure](/docs/concepts/selective-disclosure) and derived assertions are so effective: a relying party that can rely on a high-assurance, well-bound credential rarely needs the underlying identity data at all. ## How this relates to the rest of the ecosystem [#how-this-relates-to-the-rest-of-the-ecosystem] Identity proofing and holder binding sit upstream of almost every other governance question: * They set the assurance level that [credential design](/docs/concepts/policy-considerations#what-should-and-should-not-be-credentialized) can rely on. * They underpin [lifecycle management](/docs/concepts/policy-considerations), because a credential is only worth updating or revoking if it was bound to the right person in the first place. * They interact with delegation and representation, where the person presenting a credential may not be its subject. See [Policy considerations](/docs/concepts/policy-considerations#delegation-and-representation). The chain of cryptographic trust that lets a relying party verify *which issuer* signed a credential is a separate question, covered in [Chain of trust](/docs/concepts/chain-of-trust). Identity proofing and holder binding answer the complementary question of *which person* the credential belongs to. ## Frequently asked questions [#frequently-asked-questions] ### What is the difference between identity proofing and holder binding? [#what-is-the-difference-between-identity-proofing-and-holder-binding] Identity proofing is the process of verifying a person against authoritative records to establish or update an identity record. Holder binding is the separate step of confirming that the person requesting a credential is the person that identity record belongs to, so the credential is provisioned to the correct individual. Proofing sets the assurance ceiling for any credential derived from the record. Holder binding connects a specific credential to its rightful holder at the moment of issuance. ### Should biometric data be stored in a verifiable credential? [#should-biometric-data-be-stored-in-a-verifiable-credential] No. Biometrics are most valuable as a proofing and holder-binding mechanism, not as an attribute inside the credential. Where a biometric check is used during issuance, only the outcome of that check should be reflected in the credential, such as identity confirmed. Raw biometric data, facial images, and fingerprints should not be credentialized, and the biometric used to confirm binding can be discarded once the process is complete. ### Where should holder binding take place? [#where-should-holder-binding-take-place] Holder binding can be performed by the issuance platform before a credential is delivered, or by the wallet when the credential is claimed. Both approaches can achieve the same assurance outcome. They differ in where biometric processing occurs, who controls the matching logic, and how responsibilities are allocated across the ecosystem. The important policy requirement is that the holder-binding event is performed, documented, and governed appropriately, not which actor performs it. ### Do relying parties need to repeat identity proofing? [#do-relying-parties-need-to-repeat-identity-proofing] Generally no. The policy objective is to establish confidence once and allow it to be reused. Where high-quality identity proofing and holder binding have already occurred at issuance, relying parties can trust the resulting credential without repeating identity verification. This reduces friction, lowers compliance costs, and limits how much personal information is collected and stored across the ecosystem. # Concepts URL: /docs/concepts Concepts introduce the foundational ideas and frameworks that shape digital trust. In this section, we break down the core principles—such as identity, credentials, verification, and trust models—that underpin how secure, interoperable systems work. Whether you’re just getting started or deepening your understanding, these guides are designed to give you clarity and confidence in navigating the evolving landscape of digital trust. The various MATTR platforms that offer digital trust capabilities. The stages involved in the issuance, holding, and verification of digital credentials. The key capabilities that enable digital trust across various platforms. An overview of the different credential formats supported by MATTR. The framework that ensures the integrity and authenticity of digital credentials. Key terms and definitions related to digital trust and credentials. # ISO/IEC 18013-5, 18013-7 and 23220 explained URL: /docs/concepts/iso-mdoc-standards Description: A plain-language guide to the ISO/IEC standards that underpin mobile documents - what 18013-5, 18013-7 and 23220 each cover, how they relate to each other, and which one applies to your use case. If you are designing a digital identity program that involves mobile documents, you will quickly encounter three closely related ISO/IEC standards: **18013-5**, **18013-7** and **23220**. They are often discussed together, sometimes interchangeably, but they each play a distinct role. This page explains what each standard covers, how they relate to one another, and how to decide which ones apply to your use case. ## The short version [#the-short-version] * **ISO/IEC 18013-5** defines how a mobile driver's license (mDL) is structured and presented **in person**, using short-range technologies such as Bluetooth Low Energy. * **ISO/IEC 18013-7** extends that model to **online (remote) presentation**, so the same credential can be verified over the internet or through a browser. * **ISO/IEC 23220** generalizes the foundations of 18013-5 so they apply to **any mobile document**, not only driver's licenses, and adds capabilities such as holder authentication. Together, these standards give issuers, holders, and verifiers a consistent framework for high-assurance mobile documents across both in-person and online interactions. ## ISO/IEC 18013-5: the foundation [#isoiec-18013-5-the-foundation] [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) is the foundational standard for **mobile driver's licenses (mDLs)**. It was created to bring the same level of trust as a physical driver's license into a digital, mobile form, while taking advantage of the capabilities that mobile devices and cryptography offer. It defines: * The **data model** for an mDL, including the claims (such as date of birth, document number, issuing authority) and how they are organized. * The **mDoc credential format**, including how the credential is encoded (using CBOR) and how it is signed (using COSE). * The **in-person presentation protocols**, including how a wallet and verifier device establish a session over Bluetooth Low Energy, Wi-Fi Aware, or NFC, and how the presentation request and response are exchanged. * The **trust model**, including how an issuer's signing certificate is anchored in an Issuing Authority Certificate Authority (IACA) and how verifiers establish trust in a given issuer. * Privacy features such as **selective disclosure** through salted hashed claims, which let a holder reveal only the specific claims a verifier requests. The defining context for 18013-5 is **proximity**. The verifier and the holder are in physical range of each other, and the credential exchange happens over short-range protocols. Because the cryptographic verification does not require a live lookup against the issuer, 18013-5 also supports **offline verification**, which is important for roadside checks, border controls, and other field deployments. ## ISO/IEC 18013-7: extending to online [#isoiec-18013-7-extending-to-online] [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) extends the 18013-5 model to **remote (online) presentation**. The credential format and trust model stay the same. What changes is the interaction context: instead of a face-to-face exchange over Bluetooth, the presentation happens over the internet, often through a browser. 18013-7 defines: * Transport mechanisms for online presentation of mDLs and mDocs. * Two protocol annexes built on widely adopted web standards: * **Annex C** describes mDoc device retrieval over the [W3C Digital Credentials API](/docs/verification/remote-web-verifiers/dc-api/overview), currently supported on iOS and Safari. * **Annex D** describes [OpenID for Verifiable Presentations (OID4VP)](/docs/verification/oid4vp) over the same Digital Credentials API, expected on Android and Chromium-based browsers. * Session security and binding requirements that adapt the in-person protections of 18013-5 to online contexts. The defining context for 18013-7 is **remote interaction**. A user opens a website, the website asks for an mDL, and the wallet on the user's device responds, either on the same device or across two devices (for example, scanning a QR code from a desktop with a phone). ## ISO/IEC 23220: beyond the driver's license [#isoiec-23220-beyond-the-drivers-license] The [ISO/IEC 23220 series](https://www.iso.org/standard/74910.html) takes the foundations established by 18013-5 and **generalizes them**. It is built on the assumption that the same trust, security, and presentation model used for mobile driver's licenses is valuable for many other kinds of mobile documents, including: * National identification cards * Residence permits * Vehicle registrations * Professional licenses and permits * Sector-specific credentials issued by government or regulated entities ISO/IEC 23220-4 in particular is **compatible with 18013-5 and 18013-7** while introducing additional capabilities, such as **holder authentication** (helping a verifier check that the person presenting the credential is the rightful holder). In other words, 18013-5 and 18013-7 give you the mDL. ISO/IEC 23220 gives you the ingredients to build other mobile documents that behave consistently with mDLs but are fit for a wider range of use cases. ## How the standards relate [#how-the-standards-relate] The simplest way to think about the relationship is: | Standard | Scope | Interaction context | Adds | | --------------- | --------------------------------- | -------------------- | ---------------------------------------------------------------------- | | ISO/IEC 18013-5 | Mobile driver's licenses (mDLs) | In person, proximity | mDoc format, in-person presentation, trust model, selective disclosure | | ISO/IEC 18013-7 | mDLs (and mDocs based on 18013-5) | Online, remote | Online presentation over the DC API and OpenID4VP | | ISO/IEC 23220 | Any mobile document | Both | Generalized model, holder authentication, broader credential types | They are layered and complementary, not competing alternatives. 18013-7 builds on 18013-5, and 23220 reuses the same building blocks while broadening the scope. ## Which standard do I need? [#which-standard-do-i-need] A useful way to frame the question is to start from the **use case**, not the standard: * **In-person verification of a driver's license on a phone.** ISO/IEC 18013-5 is sufficient. This is the canonical mDL use case. * **Online verification of a driver's license**, for example, opening an account or proving age on a website. You need ISO/IEC 18013-7 in addition to 18013-5. * **Both in-person and online verification of a driver's license.** You need both 18013-5 and 18013-7. Most production mDL programs will fall here. * **Verification of mobile documents beyond driver's licenses**, such as national ID cards or permits. ISO/IEC 23220 provides the generalized foundation. In practice, deployments often combine 23220 with 18013-5/7 building blocks. If you are not sure which combination fits your program, [contacting MATTR](mailto:dev-support@mattr.global) is a good way to talk through your specific situation. ## Why this layered approach matters [#why-this-layered-approach-matters] These standards exist because high-assurance identity is moving from a single channel (physical card, in-person check) into many channels at once (mobile, online, cross-device, cross-border). A layered set of standards makes this manageable: * The **credential format and trust model stay constant** across in-person and online contexts. An issuer signs an mDoc once, and that same credential can be presented in proximity (18013-5) or online (18013-7). * **Interoperability is preserved across vendors and jurisdictions.** A wallet and a verifier built in different countries by different vendors can interoperate because they follow the same standards. * **New use cases can be added without reinventing the trust model.** When a program decides to extend from driver's licenses to, for example, age verification credentials or professional permits, ISO/IEC 23220 provides a path that reuses the same foundations. ## Frequently asked questions [#frequently-asked-questions] ### What is ISO/IEC 18013-5? [#what-is-isoiec-18013-5] **ISO/IEC 18013-5** is an international standard that defines how a mobile driver's license (mDL) is structured and how it is presented and verified **in person**, using short-range protocols such as Bluetooth Low Energy, Wi-Fi Aware, or NFC. It is the foundational standard for the mDoc credential format and underpins offline, proximity-based verification. ### What is ISO/IEC 18013-7? [#what-is-isoiec-18013-7] **ISO/IEC 18013-7** is a technical specification that extends ISO/IEC 18013-5 to **remote, online verification**. It defines how an mDL or mDoc can be presented over the internet, including how the request and response messages flow between a verifier on the web and a wallet on a holder's device. **Annex C** of 18013-7 describes mDoc device retrieval over the W3C Digital Credentials API, and **Annex D** describes OpenID4VP over the same API. ### What is ISO/IEC 23220? [#what-is-isoiec-23220] **ISO/IEC 23220** is a series of standards that **generalizes** the foundations established by ISO/IEC 18013-5 so they can apply to a **broader set of mobile documents**, not only driver's licenses. It introduces additional capabilities such as **holder authentication**, and is compatible with the data structures and security mechanisms defined in 18013-5 and 18013-7. ### What is the difference between ISO/IEC 18013-5 and ISO/IEC 18013-7? [#what-is-the-difference-between-isoiec-18013-5-and-isoiec-18013-7] **ISO/IEC 18013-5** covers **in-person, proximity-based** presentation of mobile driving licenses. **ISO/IEC 18013-7** covers **remote, online** presentation over the internet. The two are complementary. They share the same credential format and signature model, but address different interaction contexts. Most real-world deployments need both. ### Which standard do I need - 18013-5, 18013-7, or 23220? [#which-standard-do-i-need---18013-5-18013-7-or-23220] If you only need in-person verification of a driver's license on a mobile device, **ISO/IEC 18013-5** is sufficient. If you also need to verify the credential online or in a browser-based flow, you need **ISO/IEC 18013-7** as well. If you are working with broader mobile documents beyond driver's licenses, such as national identification cards or sector-specific permits, **ISO/IEC 23220** provides the generalized foundation. Most production deployments draw on more than one of these standards. ### Are these ISO standards finalized? [#are-these-iso-standards-finalized] **ISO/IEC 18013-5** was published in 2021. **ISO/IEC 18013-7** was published as a technical specification in 2024 and as an international standard in 2025. Parts of **ISO/IEC 23220** are published, with others still in development. ## Summary [#summary] ISO/IEC 18013-5, 18013-7 and 23220 are not alternatives. They are layers of the same model for high-assurance mobile documents. 18013-5 establishes the mDL credential format and in-person presentation. 18013-7 extends it to online and browser-based verification. 23220 generalizes the foundation so the same model can carry other mobile documents beyond driver's licenses. Most real-world deployments rely on more than one of these standards at the same time. # What is an mDL? URL: /docs/concepts/mdl Description: Learn what a mobile Driver’s License is, how it differs from a physical license, what it enables, and how ISO/IEC 18013-5 and ISO/IEC 18013-7 support trusted in-person and online verification. A **mobile Driver’s License (mDL)** is a **driver’s license stored on a mobile device**, usually in a digital wallet app. A true mDL is not just a photo or digital copy of a plastic card. It is a **standards-based digital credential** designed to let a person share license information securely and let another party verify that information with confidence. mDLs are designed for both **in-person** and **online** verification. Standards such as **ISO/IEC 18013-5** and **ISO/IEC 18013-7** help make that possible by defining how mDL data is requested, presented, protected, and verified. ## In simple terms: what does an mDL do? [#in-simple-terms-what-does-an-mdl-do] An mDL lets a person use their phone to prove information that would traditionally be shown using a physical driver’s license. That can include proving: * identity * age * driving entitlement * address or other license-related attributes, depending on the use case and jurisdiction The main idea is simple: instead of handing over a plastic card, the holder shares verified information from a digital credential. ## How is an mDL different from a physical driver’s license? [#how-is-an-mdl-different-from-a-physical-drivers-license] A physical driver’s license is usually checked by visually inspecting it. A verifier looks at the photo, reads the details, and decides whether the card appears genuine. An mDL works differently. Because it is a digital credential built on technical standards, an mDL can support **cryptographic verification**. That means the verifier may be able to confirm: * that the credential was issued by a trusted issuer * that the data has not been changed * that the credential is being presented in a secure session * in some cases, that the credential is bound to the device presenting it This gives an mDL capabilities that a physical card does not easily provide: * stronger digital verification * more privacy-aware data sharing * online and remote presentation * support for automated or semi-automated verification workflows Another important difference is **data minimisation**. With a physical license, the whole card is usually shown even when only one fact is needed. With an mDL, a verifier can request specific data, and the holder can consent to sharing only what is required for the interaction. ## What interactions can an mDL enable? [#what-interactions-can-an-mdl-enable] An mDL can support both **in-person** and **remote** identity checks. ### In-person use cases [#in-person-use-cases] A mobile driver’s license can be used in situations such as: * roadside checks * age verification for restricted goods or venues * proving identity at a service desk * access to regulated services * identity checks in hospitality, travel, or events Some mDL workflows are designed to work **offline in real time**, which is useful where internet access is unavailable or unreliable. ### Online and remote use cases [#online-and-remote-use-cases] An mDL can also support remote workflows, including: * online account opening * remote onboarding * access to digital government services * online age checks * identity verification in financial transactions These online interactions matter because many digital services still rely on scans, uploads, and repeated manual checks. A standards-based mDL provides a more structured and trustworthy way to exchange verified identity information online. ### Same-device and cross-device workflows [#same-device-and-cross-device-workflows] Online mDL verification may happen: * on the **same device**, where the user completes a transaction and presents the credential on one device * across **multiple devices**, where one device is used for the service and another for the credential presentation This flexibility supports a wider range of service designs and user experiences. ## What standards are used for mDLs? [#what-standards-are-used-for-mdls] The two most important standards commonly discussed in relation to mDLs are **ISO/IEC 18013-5** and **ISO/IEC 18013-7**. ### ISO/IEC 18013-5 [#isoiec-18013-5] **ISO/IEC 18013-5** defines how a mobile driver’s license can be presented and verified, especially in **proximity** or in-person scenarios. It provides the technical foundation for: * the structure of the credential * the way data is exchanged * the cryptographic mechanisms used to establish trust * the ability for a verifier to validate the issuer and the credential data In practice, this is what helps distinguish a real mDL from a simple digital copy of a license card. ### ISO/IEC 18013-7 [#isoiec-18013-7] **ISO/IEC 18013-7** extends mDL capabilities to **remote or online verification**. It supports the use of mDLs over internet-based interactions, helping organisations build workflows where license data can be requested and presented securely online. This expands mDL use beyond face-to-face presentation. ### Why these standards matter [#why-these-standards-matter] These standards matter because they improve: * **trust**, by defining how credentials are verified * **interoperability**, by giving ecosystem participants a shared framework * **scalability**, by reducing the need for bespoke approaches * **consistency**, by aligning issuers, wallets, and verifiers around common models ## How do mDLs support privacy and security? [#how-do-mdls-support-privacy-and-security] mDLs are often discussed in terms of both **security** and **privacy**, because they are designed to improve how identity information is shared. ### Security features [#security-features] An mDL or mDoc-based credential can support: * **issuer authentication**, so the verifier can check who issued the credential * **device authentication**, which can help reduce risks such as cloning * **holder authentication**, such as comparing the portrait to the presenter in person * **session encryption**, which helps protect the exchange from eavesdropping in both in-person and online workflows ### Privacy features [#privacy-features] An mDL can also support: * **selective disclosure**, where only needed information is requested * **holder consent**, where the person can see what is being asked for * **reduced over-sharing**, compared with handing over a full physical card This makes mDLs relevant not only for convenience, but also for better data handling practices. ## How does an mDL relate to mDocs? [#how-does-an-mdl-relate-to-mdocs] A mobile driver’s license is commonly treated as a specific type of **mobile document**, or **mDoc**. The broader mDoc concept refers to standards-based digital documents that can be stored on a mobile device and shared in trusted interactions. An mDL is one important example, but similar approaches may also be applied to other documents such as national IDs, health cards, certificates, and employee IDs. In short: * **mDL** = a mobile driver’s license * **mDoc** = a broader category of mobile documents, which can include mDLs ## Why are mDLs important? [#why-are-mdls-important] mDLs are important because people and organisations increasingly need to exchange identity information in ways that are secure, digital, and usable across channels. ### They bring trusted identity into digital channels [#they-bring-trusted-identity-into-digital-channels] Many important interactions now happen online, but identity checking processes are often fragmented or weak. mDLs provide a path toward more reliable digital verification. ### They can improve user experience [#they-can-improve-user-experience] Instead of repeatedly uploading scans or manually entering data, users may be able to share verified information more directly and consistently. ### They can reduce fraud and compliance risk [#they-can-reduce-fraud-and-compliance-risk] Because mDLs support stronger verification and secure exchange, they can help organisations improve assurance and reduce some forms of misuse or tampering. ### They create a bridge between physical and online identity use [#they-create-a-bridge-between-physical-and-online-identity-use] An mDL is familiar enough to be understandable to everyday users, but technically capable enough to support modern digital trust workflows. That makes it a practical entry point into broader digital credential ecosystems. ## Frequently asked questions [#frequently-asked-questions] ### What does mDL stand for? [#what-does-mdl-stand-for] **mDL** stands for **mobile driver’s license**. It refers to a digital version of a driver’s license stored on a mobile device and designed for secure, standards-based sharing and verification. ### Is an mDL just a digital copy of a driver’s license? [#is-an-mdl-just-a-digital-copy-of-a-drivers-license] No. A true mDL is not just a photo, screenshot, or PDF of a physical card. It is a digital credential designed to be verified using technical standards and cryptographic protections. ### Can a mobile driver’s license be used online? [#can-a-mobile-drivers-license-be-used-online] Yes. Modern mDL implementations are increasingly designed to support **remote and online verification**, especially through standards such as **ISO/IEC 18013-7**. ### Can a mobile driver’s license be used in person? [#can-a-mobile-drivers-license-be-used-in-person] Yes. In-person or proximity presentation is a core use case for mDLs, especially under **ISO/IEC 18013-5**. ### What is the difference between ISO/IEC 18013-5 and ISO/IEC 18013-7? [#what-is-the-difference-between-isoiec-18013-5-and-isoiec-18013-7] **ISO/IEC 18013-5** focuses on how mDLs are presented and verified, especially in in-person interactions. **ISO/IEC 18013-7** extends that model to support remote and online verification over the internet. ### Are mDLs more secure than physical driver’s licenses? [#are-mdls-more-secure-than-physical-drivers-licenses] They can be more secure in important ways because they can support cryptographic verification, secure sessions, and more controlled data sharing. However, the exact security outcomes depend on the implementation, the issuer, the wallet, and the verifier setup. ### Do mDLs support selective disclosure? [#do-mdls-support-selective-disclosure] Yes. One of the key benefits of an mDL is that a verifier can request only the data needed for a transaction, rather than requiring the holder to reveal the full contents of a physical card. ### Can mDLs work without internet access? [#can-mdls-work-without-internet-access] Some in-person mDL workflows can support **real-time offline verification**, depending on the interaction model and implementation. ### Are mDLs the same as mDocs? [#are-mdls-the-same-as-mdocs] Not exactly. An **mDL** is a specific kind of **mDoc**. An **mDoc** is a broader category of mobile document that can include mobile driver’s licenses as well as other digital credentials. ### Why are people interested in mDLs now? [#why-are-people-interested-in-mdls-now] Interest in mDLs is growing because organisations need better ways to support secure digital interactions across both physical and online channels, while also improving privacy, reducing fraud risk, and meeting rising expectations for digital services. ## Summary [#summary] A **mobile driver’s license (mDL)** is a standards-based digital version of a driver’s license stored on a phone. Unlike a photo or digital copy of a physical card, an mDL is designed for secure, verifiable data sharing. It can support in-person and online identity checks, selective disclosure of information, and stronger privacy and security through standards such as **ISO/IEC 18013-5** and **ISO/IEC 18013-7**. # mDocs beyond the driver's license URL: /docs/concepts/mdocs-beyond-mdl Description: How the foundations established by mobile driver's licenses (mDLs) extend to broader mobile documents such as national identification cards, residence permits, and professional licenses, and how ISO/IEC 23220 supports that transition. The **mobile driver's license (mDL)** is often the first credential people encounter when learning about mobile documents. That makes sense. mDLs are widely recognized, regulated, and already in production in many jurisdictions. But it is important not to mistake the starting point for the end state. The standards, infrastructure, and user experience patterns developed for mDLs are designed to apply to a much wider class of credentials: **mobile documents**, or **mDocs**. This page explains how mDocs extend beyond the driver's license, the role ISO/IEC 23220 plays in that extension, and why this matters for organizations planning digital identity programs. ## The mDL as a starting point, not the end state [#the-mdl-as-a-starting-point-not-the-end-state] There are good reasons that so many digital identity programs have begun with the mDL: * **Familiarity.** A driver's license is a credential most people already understand and carry today. Holders, verifiers, and operations teams can intuitively grasp what an mDL is for and what it should look like. * **Regulatory clarity.** Driver's licenses are issued under well-defined authorities, with established processes for identity verification, issuance, and renewal. The governance model translates relatively cleanly into a digital form. * **Standards maturity.** [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) was published in 2021 and gave the ecosystem a stable, internationally agreed standard to build against. Wallets, verifier SDKs, and certification programs have been built around it. * **General-purpose identity utility.** In many jurisdictions, the driver's license is already used informally as a general-purpose identity document. Putting it on a phone is a natural extension. Starting with the mDL lets issuers, wallets, and verifiers prove out their trust infrastructure, wallet experience, and integration patterns on a credential everyone already understands. It also produces a working ecosystem that the rest of a digital identity program can plug into. The mDL is, in other words, an excellent **first** mobile document. It is not meant to be the **only** one. ## What does "beyond the driver's license" actually mean? [#what-does-beyond-the-drivers-license-actually-mean] Once an issuer, wallet, and verifier are comfortable with mDLs, the same underlying capabilities open the door to many other credential types. These include, for example: * **National identification cards**: the closest natural extension of the mDL pattern, often issued by the same or adjacent authorities. * **Residence permits and travel documents**: credentials that today often involve physical cards and in-person checks. * **Vehicle registration and ownership documents**: credentials that benefit from being presented alongside or independently of a driver's license. * **Professional licenses and permits**: for example, regulated occupations such as healthcare, law, transport, or trades. * **Sector-specific government records**: hunting and fishing permits, firearms licenses, social benefit entitlements, and so on. * **Other high-assurance credentials** where strong issuer authentication, device binding, selective disclosure, and offline verifiability are valuable. All of these can be carried as mDocs because mDocs are not a driver's-license-specific format. They are a general-purpose, high-assurance digital credential format that happens to have been standardized first for driver's licenses. ## Why mDocs work well for credentials beyond mDLs [#why-mdocs-work-well-for-credentials-beyond-mdls] The same properties that make mDocs a good fit for driver's licenses make them a good fit for the broader class of mobile documents: * **Strong issuer authentication.** Verifiers can cryptographically check who issued the credential and that the data has not been tampered with. This matters as much for a national ID or a professional license as it does for a driver's license. * **Device binding.** The credential is bound to the holder's device, which makes cloning much harder. For regulated credentials, this is often a hard requirement. * **Selective disclosure.** A holder can share only the specific facts a verifier needs, not the whole credential. A health professional might prove their registration status without revealing every detail of their record. * **Offline-capable verification.** Verification does not require a live call to the issuer. This matters for field deployments such as border control, roadside checks, or remote service delivery. * **In-person and online presentation.** ISO/IEC 18013-5 handles proximity, ISO/IEC 18013-7 extends to online, and the same credential can flow through both. This is just as useful for a residence permit as it is for a driver's license. These properties are properties of the **format and trust model**, not of any one credential type. ## How ISO/IEC 23220 enables the move beyond mDLs [#how-isoiec-23220-enables-the-move-beyond-mdls] The standards community recognized early on that the model created for mDLs would need to generalize. That is the role of [ISO/IEC 23220](https://www.iso.org/standard/74910.html), a series of international standards that generalizes the mDoc model. ISO/IEC 23220: * **Reuses the same building blocks** as ISO/IEC 18013-5 (CBOR encoding, COSE signing, IACA-anchored trust, salted hashed claims for selective disclosure). * **Decouples the model from the driver's license.** Instead of describing mDL-specific data, 23220 talks about mobile documents in general. * **Adds capabilities** such as **holder authentication**, which strengthens the link between the credential and the person presenting it. * **Stays compatible with 18013-5 and 18013-7**, so wallets and verifiers that already support mDLs can extend to other mDocs without reinventing their infrastructure. The takeaway is that ISO/IEC 23220 lets ecosystem participants reuse what they have built for mDLs and apply it to a wider range of credentials. For a deeper look at how 18013-5, 18013-7 and 23220 fit together, see the [ISO/IEC 18013-5, 18013-7 and 23220 explained](/docs/concepts/iso-mdoc-standards) page. ## What this means in practice [#what-this-means-in-practice] For organizations planning a digital identity or credential program, the practical consequence is straightforward: an mDL program can be designed as the **first step** in a broader credential strategy, not as a standalone product. That implies a few useful planning principles: * **Choose infrastructure that is not mDL-specific.** Wallets, verifier SDKs, and trust registries that only support mDLs will block future expansion. Look for solutions that support mDocs in general, including 23220-aligned credentials. * **Design issuance processes that can carry other credentials.** Many of the same identity proofing, claims sourcing, and key management workflows that issue an mDL can also issue other mDocs. Build them with that in mind. * **Plan verifier acceptance broadly.** Verifiers that accept mDLs today can usually be extended to accept other mDocs with relatively modest changes, provided the underlying format support is in place. * **Think about holder experience across credentials.** Holders will eventually carry multiple mDocs in a single wallet. Wallet selection and credential presentation flows should anticipate this. ## How MATTR supports mDocs beyond the driver's license [#how-mattr-supports-mdocs-beyond-the-drivers-license] MATTR's mDoc format is **generic**. It is not restricted to driver's license claims. The same MATTR VII issuance and verification capabilities used to support an mDL program can also be used to issue and verify other mobile documents, including identity cards, permits, and sector-specific credentials. MATTR works with governments, ecosystem partners, and large organizations on credential programs that go beyond mDLs. If you are planning a program that starts with the mDL but anticipates other mobile documents over time, or that begins directly with another mDoc type, [contact us](mailto:dev-support@mattr.global) to talk through the design. ## Frequently asked questions [#frequently-asked-questions] ### Are mDocs only for driver's licenses? [#are-mdocs-only-for-drivers-licenses] No. The **mobile driver's license (mDL)** is the most established example of an mDoc, but the underlying credential format, security mechanisms, and presentation protocols are designed to carry a much wider range of mobile documents. **ISO/IEC 23220** generalizes these foundations so they apply to any high-assurance mobile document. ### What kinds of credentials can be issued as mDocs beyond mDLs? [#what-kinds-of-credentials-can-be-issued-as-mdocs-beyond-mdls] mDocs are well-suited to any high-assurance identity or entitlement credential that benefits from cryptographic verification, device binding, selective disclosure, and offline presentation. Examples include **national identification cards**, **residence permits**, **vehicle registrations**, **professional licenses**, **sector-specific permits**, and other government-issued records. ### Why is the mDL such a common starting point? [#why-is-the-mdl-such-a-common-starting-point] Driver's licenses are widely recognized, regulated, and already used as a general-purpose identity document in many jurisdictions. **ISO/IEC 18013-5** standardized them first, and many governments have launched mDL programs as an entry point into broader digital identity ecosystems. The mDL gives issuers, wallets, and verifiers a concrete first credential to test patterns, infrastructure, and user experience before extending to other mobile documents. ### What is ISO/IEC 23220 and how does it help? [#what-is-isoiec-23220-and-how-does-it-help] **ISO/IEC 23220** is a series of international standards that generalizes the model established by ISO/IEC 18013-5 so it can apply to **any mobile document**, not only mDLs. It is compatible with 18013-5 and 18013-7 while introducing additional capabilities such as **holder authentication**. This lets issuers reuse the same trust model, wallets, and verification infrastructure across many credential types. ### Can MATTR help me extend an mDL program to broader mDocs? [#can-mattr-help-me-extend-an-mdl-program-to-broader-mdocs] Yes. MATTR's mDoc format is **generic** and can carry the claims required for many credential types beyond driver's licenses. MATTR works with governments and ecosystem partners on programs that go beyond mDLs, including national IDs and other regulated credentials. [Contact us](mailto:dev-support@mattr.global) to discuss your specific use case. ## Summary [#summary] The mobile driver's license is an excellent first credential, not the only credential. The same standards, infrastructure, and user experience patterns built for mDLs apply to a much broader class of mobile documents, from national IDs to residence permits to professional licenses. ISO/IEC 23220 generalizes the foundations of 18013-5 so the same trust model and tooling can carry these other credentials. For organizations planning a digital credential program, the practical implication is to design with mobile documents in general in mind, with the mDL as an early step rather than the destination. # Platforms URL: /docs/concepts/platforms MATTR helps build trust into the digital world, ensuring that interactions remain seamless, secure, and privacy preserving. By integrating our products with your procedures, policies and business logic, you can streamline the management of digital credentials across their [lifecycle](/docs/concepts/credential-ecosystem#credential-lifecycle). To support this, we offer three key platforms: * **MATTR VII** is a cloud-based platform offering scalable digital trust services through advanced APIs and a web [Portal](/docs/platform-management/portal), catering to credential issuers, verifiers and network providers. * **MATTR Pi** provides SDKs for creating bespoke experiences or extending existing applications. Ideal for those requiring cross-platform and cross-channel integrations. * **MATTR GO** offers a quick-start solution with configurable white-label apps, perfect for early-stage network operators, wallet providers and verifiers aiming for rapid deployment without extensive resource allocation. Each platform is designed to fit different needs within the digital trust ecosystem, helping you to embed digital credentials into your solution. ## Choosing the right platform [#choosing-the-right-platform] The three platforms address different layers of a credential ecosystem and most production deployments use more than one of them together. The decision usually comes down to three trade-offs: how much you want to build yourself, how distinctive the end-user experience needs to be, and how quickly you need to reach production. | If you need to... | Start with | | -------------------------------------------------------------------------------- | ----------------------- | | Issue, manage, or verify credentials as a service for your ecosystem | [MATTR VII](#mattr-vii) | | Embed credential holding or verification into your own mobile or web application | [MATTR Pi](#mattr-pi) | | Launch a branded holder or verifier app quickly without writing code | [MATTR GO](#mattr-go) | ## MATTR VII [#mattr-vii] MATTR VII is the cloud platform that issuers, verifiers, and ecosystem operators build on. It exposes the full credential lifecycle through REST APIs and a web [Portal](/docs/platform-management/portal), and provides the services Pi SDKs and GO apps depend on for backend operations. **Who it is for** * Government agencies and commercial credential issuers operating at scale. * Relying parties running web or service-based verification. * Ecosystem operators (trust framework providers, network coordinators) orchestrating issuers and verifiers across a community. **What it provides** * [Credential lifecycle](/docs/concepts/capabilities) services for issuance, management, and verification across multiple [credential profiles](/docs/concepts/formats-overview), including mDocs (ISO/IEC 18013-5 and 23220) and CWT credentials. * [Digital Trust Services](/docs/digital-trust-service) that expose signed lists of trusted issuers and verifiers, including PKI for [chain of trust](/docs/concepts/chain-of-trust) and trust registry capabilities across ecosystem members. * [Platform management](/docs/platform-management), including [custom domains](/docs/platform-management/custom-domain-overview), a web based [Portal](/docs/platform-management/portal), and public key infrastructure. Choose VII when the credential workflow itself, not just the user-facing application, is what you need to operate. Issuance policy, revocation, trust configuration, and verifier resolution all live here. Read the [MATTR VII spec sheet](https://files.mattr.global/specsheets/vii-overview.pdf) for more. ## MATTR Pi [#mattr-pi] MATTR Pi is a set of SDKs that embed credential holding and verification into your own mobile and web applications. The SDKs abstract standards compliance, cryptography, transport flows, and platform-specific behaviors so your team can focus on the user experience. **Who it is for** * Wallet providers building a distinctive brand experience, or integrating credentials into an existing consumer app. * Verifier organizations that need credential verification inside their own iOS, Android, or web application (for example at a kiosk, point-of-sale, or front-of-house workflow). * Development teams with the engineering capacity to own the application layer while leveraging MATTR's standards implementation. **What it provides** * Holder SDKs for iOS, Android, and React Native to claim mDocs using [OID4VCI](/docs/issuance) flows, store them on device, present them in person (per ISO/IEC 18013-5) and remotely (per ISO/IEC 18013-7), and manage credential authentication policies (biometrics, PIN, passcode). * Verifier mobile SDKs for iOS, Android, and React Native, supporting QR and NFC device engagement and offline in-person verification per ISO/IEC 18013-5, and remote mobile-based verification of same-device holder apps per ISO/IEC 18013-7 using OID4VP and the W3C Digital Credentials API where supported. * A verifier web SDK for browser-based remote verification using ISO/IEC 18013-7-aligned flows, including OID4VP and the [W3C Digital Credentials API](/docs/concepts/decentralized-trust-model#the-technology-that-enables-decentralized-trust) where supported. Pi works alongside VII, which provides the backend services for credential exchange, verification policy, and issuer resolution. Read the spec sheets for the [Holder SDK](https://files.mattr.global/specsheets/pi-overview.pdf), [Verifier Mobile SDK](https://files.mattr.global/specsheets/pi-overview.pdf), and [Verifier Web SDK](https://files.mattr.global/specsheets/pi-overview.pdf). ## MATTR GO [#mattr-go] MATTR GO is a white-label app platform for organizations that need production-ready holder and verifier apps under their own brand without commissioning custom development. Apps are built from proven templates and configured for your environment, inheriting MATTR's underlying security architecture and standards implementation. **Who it is for** * Government departments and commercial schemes launching credential-enabled apps. * Organizations prioritizing time-to-market and a branded presence in app stores. * Service providers that want a turnkey app with the flexibility to evolve later. **What it provides** * **GO Hold**: a branded holder app that claims, stores, and presents credentials in person and remotely, with [selective disclosure](/docs/concepts/selective-disclosure), PIN or biometric protection, and revocation handling. * **GO Verify**: a branded verifier app for in-field validation, including offline operation and visual signals for credential validity and expiry. * Flexible service models: a *managed* model where MATTR delivers signed app bundles for you to publish, or a *self-managed* model where MATTR signs and submits apps to Apple and Google stores on your behalf. * A design asset bundle covering icons, splash screens, typography, and layout templates. Choose GO when the application layer is not where you want to invest engineering effort and you need branded apps in users' hands quickly. Read the [MATTR GO spec sheet](https://files.mattr.global/specsheets/go-overview.pdf) for more. ## Combining platforms [#combining-platforms] The platforms compose rather than substitute. Common combinations include: * **VII + Pi**: a custom wallet or verifier app built with Pi SDKs, backed by VII for issuance, exchange, and verification policy. Typical when you need a distinctive end-user experience and operate your own credential services. * **VII + GO**: a branded white-label app backed by your own VII tenant. Typical when you want both ecosystem services and user-facing apps under one operating model, without engineering the apps themselves. * **VII alone**: a backend-only deployment where end users interact through third-party wallets and verifiers. Typical for public schemes (for example, an mDL program) where citizens use the compliant wallet of their choice. Choosing the right platform for you depends on your use case, requirements, resources and implementation timelines. [Contact us](mailto:dev-support@mattr.global) to discuss your options. # Policy considerations for credential ecosystems URL: /docs/concepts/policy-considerations Description: How verifiable credentials reshape existing policy concerns, what the architecture handles structurally, what remains in the policy domain, and where to begin. Verifiable credentials change the architecture of identity verification. They do not eliminate the need for policy. They change where the policy work sits, which existing safeguards still do the work, and which new questions arise that did not exist in the centralized model. This page is written for policymakers, regulators, and issuing agencies thinking through the governance of a credential ecosystem. It complements [Decentralized trust model](/docs/concepts/decentralized-trust-model), which explains the architectural shift, by focusing on the cross-cutting policy questions that follow from it. The shape of these questions is consistent across jurisdictions. The answers are not, and should not be. Constitutional structure, regulatory tradition, and the assurance profile of each credential all matter. What follows is a map of the questions and the design choices each one opens up, rather than a prescription. ## How the credential model reshapes existing policy concerns [#how-the-credential-model-reshapes-existing-policy-concerns] The policymaker's task is not to evaluate verifiable credentials in isolation. It is to think through how a credential ecosystem affects the concerns policy already addresses. The credential model changes the shape of each one. Some get easier. Some get harder. Some change character entirely. ### Privacy [#privacy] A well-designed credential ecosystem is structurally more privacy-respecting than the centralized model it replaces, but the gain depends on design choices that policy needs to enable and enforce. [Selective disclosure](/docs/concepts/selective-disclosure) lets a verification answer a question without revealing the underlying data. A bartender confirms a customer is over 18 without learning the date of birth. A landlord confirms a tenant has the right to reside without learning the immigration history. A bank confirms a customer's identity without storing a passport scan. These outcomes are not the default. They are the result of the wallet, the credential, and the verifier all being set up to support them. Where the design is right, the privacy gain is substantial. Where the design is wrong, the credential model can be more privacy-harmful than what it replaced, because high-assurance data flows in higher volumes through more pathways. The policy task is to make sure the design is right. ### Data retention [#data-retention] The traditional pattern was for relying parties to retain copies of identity documents as evidence that a verification had been performed. The credential model makes that retention largely unnecessary. The presentation produces a cryptographically signed proof that a relying party can store as evidence the check was performed, without needing to store the underlying personal data. Where retention is required, for regulatory compliance, dispute resolution, or statutory limitation periods, it can be calibrated precisely to the use case rather than defaulting to "keep everything indefinitely". Policy can codify this by setting maximum retention periods and requiring deletion confirmation. Some jurisdictions are doing this through privacy law, others through trust framework rules, others through contractual conditions of credential access. ### Security [#security] Credentials are cryptographically signed and verifiable offline. This raises the bar significantly above documents that can be forged, photoshopped, or stolen wholesale from a database. The shift from "trust the document I am holding" to "trust the cryptographic signature" closes a category of fraud that has been costly across every jurisdiction. The new attack surfaces are different. They are concentrated at the issuance platform, which must be hardened, at the wallet, which must resist device compromise, at the relying party software, which must verify properly, and at the trust registry, which establishes who is allowed to issue and verify what. These are smaller, more defined surfaces than the diffuse surface of "every database holding a copy of every customer's documents". Security improves on balance, but the security investment must shift accordingly. ### Fraud [#fraud] The credential model materially reduces the scope for identity fraud. It also raises the stakes when something does go wrong. A fraudulent government-issued credential is more valuable than a fraudulent paper document because it carries higher assurance and is accepted in more places. Issuance processes therefore need to be more rigorous than the analog equivalents, and revocation needs to be operationally immediate when fraud is detected. The architecture supports this. The issuing authority can revoke a credential in seconds, and the revocation propagates through the ecosystem as relying parties check the credential's status. The policy work is to make sure the revocation pipeline is in place before credentials are issued at scale. The rigor of issuance itself depends on [identity proofing and holder binding](/docs/concepts/identity-proofing-and-holder-binding), which establish that a credential is issued to, and controlled by, the right person. ### Data integrity [#data-integrity] Credentials carry their own integrity guarantees. The cryptographic signature means a credential cannot be altered without detection. The relying party knows the credential came from the named issuer and has not been tampered with. This is a substantial improvement over current arrangements where data is shared between systems through APIs or batch transfers and where errors propagate silently. The credential becomes the canonical source of truth for the attribute it carries, at the moment of presentation, with the issuer's authority cryptographically attached. ### Liability [#liability] The decentralized model changes who is responsible for what, and the policy answer is not automatic. In the centralized model, the agency that mediated a verification was generally accountable for getting it right. In the decentralized model, the citizen initiates the sharing, the wallet executes it, and the relying party makes the decision. Where does liability sit when a fraudulent credential is accepted? When a legitimate credential is rejected? When a relying party misuses received information? The general answer is that liability follows the locus of action and obligation. The issuing authority is accountable for the accuracy and integrity of what it issues. The wallet provider is accountable for executing presentations faithfully and for the security of credentials in storage on the device. The relying party is accountable for verifying properly and for using the received information only within the declared purpose. Trust frameworks typically codify some of these allocations. Contractual terms of credential access cover the rest. The transition period needs particular policy attention because the old liability allocations no longer fit and the new ones are not yet familiar. ### Cost recovery [#cost-recovery] The centralized model often depends on per-verification fees, such as a certified copy from a registry, a check against an agency database, or a transaction through an identity verification service. The credential model has different economics. The cost of issuance is incurred once. The cost of verification is essentially zero per transaction. This is generally welcome for citizens and relying parties. It can disrupt revenue lines for agencies that have come to depend on per-check fees. The policy question is whether the new model needs a different funding mechanism, such as per-issuance, subscription, or central appropriation, and whether legacy fee structures need to be amended. ### Inclusion and equality [#inclusion-and-equality] This is the concern that needs the most deliberate policy work, because the credential ecosystem can magnify existing exclusion if it is not designed to address it. The model assumes a level of digital literacy, device access, and institutional trust that is not evenly distributed in any population. People without smartphones, people with low digital literacy, people with cognitive disabilities, people experiencing housing instability, and communities with reasons to distrust digital government services can all be left behind. When that happens, the failure tends to be invisible to the agencies designing the system and visible to the community workers who absorb the consequences. Three policy responses help: * **Accessibility design in the wallet itself**, going beyond formal compliance baselines. * **Supported and delegated credential models**, where a trusted intermediary can help an individual access and present their credentials without holding them. * **Analog alternative channels** that are structurally maintained and funded, not aspirationally committed to. Equity has to be a design requirement, not an edge case. ### Regulation [#regulation] Trust frameworks bring new regulatory functions into being. An accreditation authority assesses providers against the framework. Standards bodies set technical requirements. Privacy regulators retain their existing role over personal data handling. Sector regulators retain theirs over regulated industries. Most jurisdictions are running this as a multi-regulator model rather than creating a single new regulator. The coordination among them matters more than the institutional design of any single one. The relationship between the trust framework regulator, the privacy regulator, and any sector regulators is the most important inter-agency relationship in the ecosystem, and it benefits from being made explicit rather than left to emerge. ## What the architecture takes care of [#what-the-architecture-takes-care-of] A recurring observation across credential programs is that architecture does more protective work than regulation. Where the ecosystem is designed well, problematic data handling becomes structurally difficult rather than merely legally prohibited. This is a meaningful shift, because legal prohibitions depend on enforcement, and enforcement is always imperfect. Structural prevention does not. Several specific architectural features carry most of the load. ### Selective disclosure [#selective-disclosure] A credential containing many attributes can be presented in a way that reveals only the ones needed for the transaction. If the relying party never receives the date of birth, it cannot retain the date of birth, share the date of birth, or have the date of birth stolen from its database. The privacy outcome is achieved without depending on the relying party's compliance with retention rules. The instrument exists in current standards (W3C VC Data Model, SD-JWT VC, ISO/IEC 18013-5) and in all standards-compliant wallet implementations. It is the single most important protective feature in the ecosystem. See [Selective disclosure](/docs/concepts/selective-disclosure). ### Purpose-bound presentation requests [#purpose-bound-presentation-requests] OpenID for Verifiable Presentations (OID4VP) requires the relying party to declare upfront what they are asking for and why before the wallet presents anything. This is a structural filter. What cannot be requested cannot be received. It also creates an auditable record of the purpose for which information was received, which makes downstream misuse easier to detect and harder to defend. Mandating OID4VP in a trust framework turns over-collection from a rule violation into an architectural impossibility. ### Cryptographic revocation [#cryptographic-revocation] The issuing authority retains a fast, prospective instrument for invalidating credentials when source data changes or misuse is detected. Where credentials are issued with appropriate expiry dates and supported by status list infrastructure at the issuance platform, revocation propagates through the ecosystem in seconds. This is materially stronger than the historic position where revoked documents could circulate for months or years before downstream parties caught up. Importantly, it is also more powerful than surveillance. The issuing authority does not need to watch what happens at the relying party layer. It needs to be able to act decisively when it learns something has gone wrong, and the architecture gives it that. ### Anti-phone-home wallet behavior [#anti-phone-home-wallet-behavior] A wallet should not contact its supplier or the issuing authority every time a credential is presented. The issuing authority therefore has no visibility into who is presenting credentials to whom. This is not a gap. It is a feature. It prevents the wallet ecosystem from becoming a surveillance infrastructure and makes it possible for citizens to use their credentials without their government watching them do so. Trust framework rules in most jurisdictions explicitly require this. ### Short expiry and reissuance [#short-expiry-and-reissuance] Designing credentials with appropriate expiry windows means stored attributes become stale quickly. A relying party that wants to rely on a credential attribute over time must reverify periodically rather than treat the original presentation as a permanent record. This creates natural disposal cycles and makes long-term retention less valuable. ### Coordinated disposal through revocation [#coordinated-disposal-through-revocation] When an issuing authority revokes a credential, the revocation notification can be propagated to relying parties bound by issuer terms, triggering contractual obligations to delete derived attributes they have retained. This creates a coordinated disposal mechanism that has no analog in the centralized model. The technical capability exists. The contractual framework that makes it legally operative is the remaining work in most jurisdictions. It helps to be precise about what revocation is. From a standards perspective, revocation is a **status signal** that a credential should no longer be trusted. A revoked credential can still be presented, but verification returns an invalid result, which prevents future successful use and future collection of data through that credential. Revocation does not, by itself, force a wallet to delete a credential, force a relying party to delete information already collected, or restrict a service. Each of those downstream consequences is a separate policy decision that must be defined explicitly through issuer terms, trust framework requirements, or relying party obligations. The technical signal exists; its legal and operational meaning is a choice. That choice should be made with parity to physical-document processes in mind. If a revoked physical document had previously been sighted or copied, there would generally be no way to notify every organization that saw it. Digital credentials make coordinated downstream action newly feasible, but feasibility is not the same as obligation. Issuing authorities should apply coordinated consequences where they are genuinely necessary and proportionate to credential sensitivity, rather than imposing stronger retrospective obligations on digital interactions simply because the technology allows it. ### Zero-knowledge proofs [#zero-knowledge-proofs] On a longer horizon, zero-knowledge proofs take the data minimization principle to its logical endpoint. A credential can be used to produce a cryptographic proof that a condition is satisfied without revealing any underlying attribute. The W3C Verifiable Credentials Data Model 2.0 supports ZKP-compatible formats. This is the direction of travel for a category of high-volume, low-sensitivity verifications, such as age confirmation, eligibility checks, and residency, where even derived predicates may be more than the relying party needs. The policy implication of all this is that architecture takes care of a great deal. Where regulators in the centralized model spend significant effort trying to police downstream behavior through retention rules, purpose limitation, and complaint-driven enforcement, the credential ecosystem does much of that work structurally. Policy attention can be freed up for the questions architecture cannot answer. ## What policy still needs to address [#what-policy-still-needs-to-address] The architecture, however well designed, does not answer everything. Several categories of question remain firmly in the policy domain. These are the issues most jurisdictions are still working through. ### Trust framework scope and the relying party gap [#trust-framework-scope-and-the-relying-party-gap] The architecture provides the tools. The trust framework decides who is bound by which rules. In most jurisdictions, accredited credential services (issuers and wallets) are bound to a coherent set of obligations. Relying parties typically are not, except through general privacy law. This is the most consequential open policy question across credential programs. The choices include: * **Extending the trust framework** to bring relying parties inside it. A hard sell politically and operationally. * **Tiering accreditation** so that high-assurance credentials can only be received by accredited parties. A workable compromise. * **Imposing binding terms on relying parties as a contractual condition of credential access**. Available immediately, but contractual rather than regulatory. Most jurisdictions are landing on some combination of these. The right combination depends on the assurance profile of the credentials being issued and the regulatory capacity of the jurisdiction. There is no universal answer. ### What should and should not be credentialized [#what-should-and-should-not-be-credentialized] Just because an issuing agency holds data does not mean that data should flow into a credential. Several categories of information warrant exclusion from the default credential attribute set: * **Ethnicity** is a special category under most privacy regimes, carries specific discrimination risk, and should be excluded unless explicitly opted into for a defined purpose. * **Sensitive relationship information** held in birth registers can expose third parties (parents, siblings) who did not consent. * **Detailed immigration status** can be misused in ways that affect employment, housing, and access to services. * **Health and disability inferences** should not appear in identity credentials regardless of source. * **Biometric templates** should not be credentialized. Only the outcomes of biometric verification ("identity confirmed: yes") should be. * **Information the issuing agency holds but is not authoritative for** should be credentialized by the authoritative source agency, not by the holder. These are policy choices, not technology choices, and they need to be made explicitly at the credential design stage. ### Inclusion architecture [#inclusion-architecture] The technology cannot solve digital exclusion on its own. Policy needs to make explicit provision for accessibility, supported credential use, delegated authority models (where a trusted intermediary holds credentials for someone who cannot self-manage), and maintained analog channels. The community organizations that absorb digital exclusion failures need to be in the design conversation rather than discovered later as an edge case. Equity impact assessments before each major rollout phase, public reporting on who is using the credential ecosystem and who is not, and funded analog alternatives with service-level standards are all useful instruments. None of these are technology decisions. ### Delegation and representation [#delegation-and-representation] Much of the discussion around credentials assumes a person presenting a credential about themselves. Many real-world interactions do not fit that assumption. Parents act for children, guardians act for dependents, carers support vulnerable individuals, and authorized representatives act under powers of attorney. These are not edge cases. They are common interactions across public services, regulated sectors, and private service delivery, and a credential ecosystem needs to support them from the outset rather than treating them as exceptions to be addressed later. Technically, a credential is always issued to a specific person and represents information about that person. Delegation introduces a distinction between the credential subject and the person authorized to hold, manage, or present that credential. The technology can support this distinction. The harder questions are legal and policy questions: who is permitted to act on behalf of whom, under what authority, for what purpose, and for how long. Existing legal authorities, such as guardianship arrangements and enduring powers of attorney, provide much of the answer, but policy decisions are still required about which relationships the issuing authority will recognize, what evidence establishes them, and how they are maintained over time. Delegation is best treated as a lifecycle issue rather than a one-time decision. Authority may be granted, modified, suspended, revoked, or expire. The transition to adulthood illustrates this well: a parent may legitimately manage a child's credentials today, but that authority should not continue indefinitely, and the policy framework should define when and how responsibility transitions to the individual. Wallet design plays a role here too, since wallets may need to support multiple credential subjects and clearly distinguish personal credentials from those held under delegated authority. Delegation also intersects with the relying party gap and with inclusion. A relying party may need assurance not only that a credential is valid, but that the person presenting it is authorized to act on behalf of the subject, communicated in a privacy-preserving way that does not require excessive disclosure of personal or legal information. And because many individuals may never manage credentials independently, delegation is one of the mechanisms that makes broad participation possible. A system that assumes every person can self-manage digital credentials risks excluding significant parts of the population. Delegated authority is therefore best understood as a first-order design consideration rather than a feature to add later. ### Inter-agency coordination [#inter-agency-coordination] Where the issuing authority and the issuance platform sit in different agencies, which is increasingly common as governments separate identity policy from digital service delivery, the coordination protocol between them needs explicit design. How does the issuing authority instruct the platform to revoke a credential? On what evidence? Against what timing? What data sharing is permitted between them? Who decides credential policy? Who maintains the trust registry? These are not technology questions. They are inter-agency governance questions, and they are best resolved before the credential program scales rather than after. ### Cross-border interoperability [#cross-border-interoperability] No jurisdiction issues credentials only for use within its borders. Mobile driving licenses from one jurisdiction are increasingly accepted at airports and other contexts in another. ISO/IEC 18013-5 provides the technical compatibility layer for mDL. The W3C VC Data Model and emerging eIDAS-compatible formats provide it for broader credentials. The policy work is still maturing. Bilateral and multilateral arrangements for mutual recognition, joint accreditation of wallets and issuers, and shared assurance frameworks are emerging unevenly. The technology is interoperable by design. The policy framework for cross-border recognition is the remaining gap. ### Liability allocation during transition [#liability-allocation-during-transition] During the period where both centralized and decentralized verification are operating in parallel, policy needs to address what happens when something goes wrong in the new model, who is responsible, and how recourse works. Trust framework rules can codify some of this. Contractual issuer terms can address some. Sectoral regulation may need updates. Litigation will eventually settle the rest, but policy work upfront reduces the cost of getting there. ### Enforcement and consequences [#enforcement-and-consequences] Where general privacy law is the principal restraint on relying party behavior, jurisdictions are realising that privacy law's enforcement architecture was designed for a different set of harms. Many privacy regimes do not include meaningful financial penalties for ordinary breaches. Enforcement is complaint-driven and retrospective. The credential ecosystem raises the question of whether the enforcement floor is sufficient when high-assurance government-issued identity information is involved. This is a legislative question in most jurisdictions. ### Platform layer governance [#platform-layer-governance] Jurisdictions are taking different positions on wallet governance. Some operate a single state wallet. Others have chosen explicit multi-wallet choice. Others are designing for federated wallets across constituent jurisdictions. Each model has trade-offs: * **Single state wallets** simplify governance and accountability but raise competition concerns and create single points of failure. * **Multi-wallet ecosystems** preserve choice and resilience but increase coordination complexity. * **Federated models** distribute governance but require strong cross-jurisdiction agreement. There is no single right answer. The policy choice is real and consequential and should be made deliberately, with the trade-offs understood. ## A starting position for issuing agencies [#a-starting-position-for-issuing-agencies] For an issuing agency considering whether and how to begin credentialling its identity holdings, a few practical observations apply across the programs in production today. ### The technology is ready [#the-technology-is-ready] Verifiable credential infrastructure has matured to the point that platform implementation is no longer the bottleneck. Standards-compliant issuance platforms, wallets, and verification tools have been deployed at scale in several jurisdictions. An agency starting now is not pioneering the technology. It is making policy and design choices within a known technical envelope. ### The policy work is most of the work [#the-policy-work-is-most-of-the-work] The harder questions are not "can we issue verifiable credentials" but "what should the credential contain, who should be able to receive it, under what conditions, and what happens when something goes wrong". These are decisions only the issuing agency and its regulator can make. They benefit from being made early rather than late. ### Start with a defined use case [#start-with-a-defined-use-case] The most successful programs have not tried to do everything at once. A focused initial use case, such as a driving license or a single identity attestation, lets the agency develop the operational muscle, including revocation, lifecycle management, and relying party coordination, before the credential set expands. ### Build the governance instruments before scale [#build-the-governance-instruments-before-scale] The technical platform can be implemented quickly. The credential policy, the trust registry, the binding terms for relying parties, the revocation protocols, the coordination with the privacy regulator, the equity impact assessment, the accessibility plan, and the inter-agency coordination protocol all take longer and are harder to retrofit. The mistake to avoid is launching the platform before the governance instruments are in place. ### Choose architecture over regulation where you can [#choose-architecture-over-regulation-where-you-can] For every concern that arises, the first question is whether the architecture answers it. Selective disclosure answers many privacy concerns. OID4VP answers many over-collection concerns. Cryptographic revocation answers many accuracy concerns. Where the architecture answers a concern, the policy task is to make sure the architectural choice is mandated rather than recommended. Where the architecture does not answer the concern, policy needs to do the work. The discipline is to keep the two questions in the right order. ### Plan for relying party governance from day one [#plan-for-relying-party-governance-from-day-one] This is the area where almost every jurisdiction has had to come back and add governance later. The architectural instruments (selective disclosure, OID4VP, short expiry) reduce the relying party governance burden but do not eliminate it. Binding issuer terms imposed through the trust registry as a condition of credential access are among the most powerful tools available within current law, and they should be drafted in parallel with the credential design itself. ### Take inclusion seriously as a design requirement [#take-inclusion-seriously-as-a-design-requirement] The programs that have succeeded for the broadest population have made inclusion a design constraint from the outset, not an add-on. The programs that have struggled have learned the cost later. ### Coordinate internationally where possible [#coordinate-internationally-where-possible] ISO/IEC 18013-5, W3C VC Data Model 2.0, OID4VP, and SD-JWT VC are the technical lingua franca of the global ecosystem. Aligning with these standards from the beginning protects against jurisdictional lock-in and enables cross-border recognition later. Where bilateral or multilateral relationships are particularly important, early coordination on accreditation and mutual recognition pays off. ### Expect the policy framework to evolve [#expect-the-policy-framework-to-evolve] Every credential program reviewed to date has revised its trust framework rules and accompanying guidance multiple times as the program has matured. This is healthy, not a sign of failure. The technology and the use cases are still developing, and the policy framework needs to evolve with them. Build the policy infrastructure (the regulator function, the standards-setting body, the consultation mechanisms) for ongoing evolution, not as a one-off settlement. ## Summary [#summary] Verifiable credentials are not a magic privacy or security solution. They are an infrastructure for moving identity verification from a model based on document exchange and centralized mediation to one based on cryptographic proof and citizen control. They make some things structurally easier (privacy, fraud reduction, data minimization) and some things newly important (issuer governance, relying party accountability, revocation operations, equity by design). The policy work is real, and it sits primarily with the issuing agency and the trust framework regulator. The credential model is operational, the standards are mature enough to commit to, and the policy framework can be built in stages alongside the technical rollout. ## Frequently asked questions [#frequently-asked-questions] ### Do verifiable credentials replace privacy law? [#do-verifiable-credentials-replace-privacy-law] No. Verifiable credentials change where privacy protection is applied. Architectural features such as selective disclosure, purpose-bound presentation requests, and anti-phone-home wallet behavior do much of the work that retention rules and purpose limitation did under privacy law. But privacy law still applies to relying parties that receive credentials and to any personal data they retain after a presentation. ### Who is liable when something goes wrong with a verifiable credential? [#who-is-liable-when-something-goes-wrong-with-a-verifiable-credential] Liability typically follows the locus of action and obligation. The issuing authority is accountable for the accuracy and integrity of what it issues. The wallet provider is accountable for executing presentations faithfully and for the security of credentials on the device. The relying party is accountable for verifying properly and for using received information only within the declared purpose. Trust frameworks usually codify some of these allocations, and contractual terms of credential access cover the rest. ### Are relying parties bound by the trust framework? [#are-relying-parties-bound-by-the-trust-framework] In most jurisdictions, accredited credential services such as issuers and wallets are bound to the trust framework. Relying parties typically are not, except through general privacy law. Closing this relying party gap is one of the most consistent open policy questions across credential programs. Options include extending the trust framework, tiering accreditation, or imposing binding terms on relying parties as a contractual condition of credential access. ### What data should not be put into a verifiable credential? [#what-data-should-not-be-put-into-a-verifiable-credential] Several categories warrant exclusion from the default credential attribute set. These include ethnicity, sensitive relationship information that can expose third parties, detailed immigration status, health and disability inferences, and biometric templates. Information the issuing agency holds but is not authoritative for should be credentialized by the authoritative source, not by the holder. ### How should an issuing agency start a verifiable credential program? [#how-should-an-issuing-agency-start-a-verifiable-credential-program] Start with a defined use case rather than trying to do everything at once. Build the governance instruments, including credential policy, the trust registry, relying party terms, revocation protocols, and the inclusion plan, before the platform scales. Choose architectural safeguards over regulation where possible. Plan for relying party governance from day one. Coordinate internationally by aligning with ISO/IEC 18013-5, the W3C VC Data Model 2.0, OID4VP, and SD-JWT VC. ### Do verifiable credentials risk excluding people without smartphones? [#do-verifiable-credentials-risk-excluding-people-without-smartphones] Yes, if inclusion is not treated as a design requirement. The credential model assumes a level of digital literacy, device access, and institutional trust that is not evenly distributed. Policy responses include accessibility design in the wallet, supported and delegated credential models where a trusted intermediary can help an individual present credentials, and analog alternative channels that are structurally maintained and funded. # Privacy in MATTR's architecture URL: /docs/concepts/privacy Description: How MATTR approaches privacy across issuance, holding, verification, and digital trust services, and how these principles are reinforced by the MATTR Security Framework. Privacy is a foundational property of the MATTR product stack, not an afterthought layered on top. The decentralized credential model that MATTR is built on shifts data control away from central intermediaries and toward the holder. MATTR's job is to provide the infrastructure that makes this shift practical at scale, while ensuring that the infrastructure itself does not become a new point of correlation or data accumulation. This page describes the principles that shape how MATTR thinks about privacy across the credential lifecycle. For details on specific product areas, see the dedicated privacy pages for [issuance](/docs/issuance/privacy), [holding](/docs/holding/privacy), [verification](/docs/verification/privacy), and [digital trust services](/docs/digital-trust-service/privacy). ## Privacy by design [#privacy-by-design] Privacy by design means that privacy properties are built into the architecture of a system rather than enforced through policy alone. In a federated identity model, the identity provider sees every transaction. Privacy then depends on the provider's contractual obligations and audit posture. In a decentralized credential model, the issuer is out of the transaction loop by design. The architecture itself prevents the identity provider from observing how a credential is used. MATTR products implement this principle across the credential lifecycle: * During **issuance**, MATTR VII acts as a transit platform. Claims flow through the platform to produce a signed credential, and the credential is delivered to the holder's wallet. Claim data is not retained beyond what is necessary to complete issuance. Limited information can **optionally** be retained to support credential lifecycle management. * During **holding**, credentials live on the holder's device. MATTR Pi Holder SDKs and MATTR GO use platform secure storage (Secure Enclave on iOS, Keystore on Android) to protect credentials and keys at rest. * During **verification**, the verifier checks cryptographic proofs locally. There is no requirement to contact the issuer at presentation time, and no central party is informed that a verification has taken place. * In **digital trust services**, MATTR publishes trust information so that participants can evaluate trust independently. The trust service does not sit in the transaction path and does not see individual verifications. The technical instruments that make this possible are described in [What is a decentralized trust model?](/docs/concepts/decentralized-trust-model) and [Selective disclosure](/docs/concepts/selective-disclosure). ## Data minimization [#data-minimization] Data minimization is the principle that systems should collect, transmit, and retain only the data strictly required for a defined purpose. Verifiable credentials make this practical in ways that earlier identity models could not: * **Selective disclosure** lets a holder reveal only the specific attributes a verifier needs (for example, "over 18: yes") rather than the full contents of the credential. See [Selective disclosure](/docs/concepts/selective-disclosure) for more. * **Offline verification** removes the need for verifiers to contact the issuer at presentation time. The issuer cannot observe verification activity. * **Transit-only processing** is the default for MATTR VII. The platform does not persist claims in issued credentials by default. Where persistence is needed for a specific business reason, it is a deliberate, configurable choice made by the customer. For developers building on MATTR, the practical implication is that data minimization is the path of least resistance. Following the default patterns produces a minimal data footprint without additional engineering work. ## Privacy backed by the MATTR Security Framework [#privacy-backed-by-the-mattr-security-framework] Architectural privacy properties are necessary but not sufficient. They have to be reinforced by the organizational controls that govern how MATTR builds, operates, and supports its products. MATTR operates a Privacy Information Management System (PIMS) as part of the broader MATTR Security Framework (MSF). The framework covers policies, procedures, roles, training, and tooling across cyber security, data and privacy protection, prevention, enterprise risk, and incident response. Independent assessments include: * **ISO/IEC 27001:2022** certification, maintained on an annual assessment cycle. * **SOC 2 Type 2** audit, performed annually by an independent auditor. * **Annual external code audit** of the MATTR codebase by Trail of Bits. * **Self-assessments** against ISO 27701, OWASP Top 10 Privacy Risks and ISO 29100. The MATTR platform is designed to support customers meeting their own obligations under GDPR, UK GDPR, the New Zealand Privacy Act 2020, the Australian Privacy Principles, California CCPA/CPRA, Quebec Law 25, and Switzerland's revised FADP, among other regimes. For full detail on policies, procedures, roles, retention schedules, sub-processors, and data residency, see the published [Privacy Policy](/docs/resources/terms/privacy-policy) and the [Data Processing Terms](/docs/resources/terms/data-processing-terms). MATTR's Data Management & Protection Plan is available to customers on request via [privacy@mattr.global](mailto:privacy@mattr.global). ## How privacy responsibilities are shared [#how-privacy-responsibilities-are-shared] In a typical deployment, MATTR provides the credential infrastructure and the customer configures it for a specific use case. This produces a shared responsibility model: | Responsibility | MATTR | Customer | | ----------------------------------------------------------------------- | ----- | -------- | | Platform security, encryption, sub-processor controls | Yes | | | Compliance certifications for the underlying platform | Yes | | | Deciding what credentials to issue and what claims they contain | | Yes | | Configuring user consent, retention settings, and verification journeys | | Yes | | Publishing a privacy notice to end users | | Yes | | Choosing the regional cloud deployment for data residency | Joint | Joint | The architecture removes specific privacy risks (issuer-side surveillance, central correlation across verifications) but it does not absolve customers of the responsibility to design their issuance and verification flows in line with applicable privacy law. ## Frequently asked questions [#frequently-asked-questions] ### Where is customer data stored? [#where-is-customer-data-stored] By default, MATTR does not store the personal data inside verifiable credentials. The MATTR VII platform is transactional: claims pass through it during issuance to produce a signed credential, and the credential is delivered to the holder's wallet. The platform retains a small amount of metadata for audit and lifecycle operations (such as a hash of the issued credential and a reference to the user it was issued to), but not the underlying claim values, unless the customer has explicitly configured them to be persisted. MATTR operates regional cloud deployments in the United States, Canada, Europe, Singapore, Australia, and New Zealand. These regions are primarily where customer data is **processed** in transit during issuance and verification, not where the personal data inside credentials is stored long term. Where data is processed, it stays within the boundary of the chosen AWS region except for specific, listed sub-processors that may operate in adjacent regions. The current list of sub-processors is published at [/docs/resources/terms/data-sub-processors](/docs/resources/terms/data-sub-processors). ### Is customer data encrypted? [#is-customer-data-encrypted] Yes. Sensitive data is encrypted at rest using AES-GCM and in transit using TLS. All APIs exposed by the MATTR platform require HTTPS. Access to encryption keys is treated as privileged access, with MFA, monitoring, and alerting. ### Are credentials issued by MATTR VII privacy preserving? [#are-credentials-issued-by-mattr-vii-privacy-preserving] The MATTR VII platform implements credential formats (mDocs, CWT, Semantic CWT) designed for privacy-preserving use. mDocs support selective disclosure and offline verification, letting holders share only the attributes a verifier asks for. Whether a specific deployment is privacy preserving in practice depends on how the customer configures issuance, what claims they include, and how verifiers consume the credential. The architectural building blocks support a minimal-data design, and customer configuration determines the outcome. ### Does MATTR track how credentials are used after issuance? [#does-mattr-track-how-credentials-are-used-after-issuance] No. Once a credential is issued to a holder's wallet, MATTR has no visibility into where, when, or how it is later presented. The decentralized architecture makes this technically impossible from the issuer side; the platform does not record or have access to verification events at third-party verifiers. ### Can MATTR access the data inside a verifiable credential? [#can-mattr-access-the-data-inside-a-verifiable-credential] Only in narrow circumstances. Claims pass through the platform during issuance to be signed into a credential, and the resulting credential is delivered to the holder. Claims are not persisted by default. Limited information may be retained for audit purposes (for example, a hash of the issued credential), but this does not include the underlying claim values unless the customer has explicitly configured them to be persisted. ### How does MATTR handle data breaches? [#how-does-mattr-handle-data-breaches] A data breach is classified as a security incident under the MATTR Security Framework and is managed through the documented Incident Management Procedure as a Severity 1 or Severity 2 incident. Customers are notified in line with their contractual terms and applicable regulatory requirements. The Data Breach Procedure is part of the MSF and is reviewed on an ongoing basis. ### Where can I see the full list of sub-processors? [#where-can-i-see-the-full-list-of-sub-processors] The published list is at [/docs/resources/terms/data-sub-processors](/docs/resources/terms/data-sub-processors). It includes the sub-processor's purpose, the data category processed, and the data location. # What is selective disclosure in verifiable digital credentials? URL: /docs/concepts/selective-disclosure Description: Learn how selective disclosure helps people share only the information needed for a specific interaction, and why that matters for privacy, compliance, and trust. **Selective disclosure** means a person can share **only the information needed for a specific interaction**, instead of handing over an entire credential or more personal data than necessary. For example, someone may need to prove they are over 18, that their licence is valid, or that they are eligible for a service. In each case, the verifier may only need one fact or a small set of facts. Selective disclosure helps make that possible. This matters because many identity checks still rely on collecting more information than is needed. That creates friction for users, increases privacy risk, and leaves organisations holding sensitive data they may not need to store. ## Why selective disclosure matters [#why-selective-disclosure-matters] Selective disclosure helps shift identity checking from a **collect everything** model to a **request only what is needed** model. That change has practical value for both sides of an interaction: * the **holder** does not need to reveal unnecessary personal information * the **verifier** does not need to collect, process, and protect data that is not relevant to the decision they are making This can improve privacy, reduce compliance burden, and build trust in digital interactions. Imagine a person buying an age-restricted product. With a physical card, they may have to show: * full name * date of birth * home address * licence number * photo * card expiry * other visible details But for the transaction, the retailer may only need one answer: * **Is this person over the required age?** Selective disclosure makes it possible to share only what is needed for that decision, rather than exposing the full contents of the credential. ## What problem does selective disclosure solve? [#what-problem-does-selective-disclosure-solve] In many identity workflows, organisations collect more data than they actually need. That often happens because the easiest way to verify something is to ask for a full document, a scan, or a screenshot. But this creates several problems: * people lose control over how much they share * organisations collect extra personal data they must protect * sensitive data can accumulate in systems that become attractive targets * compliance obligations become heavier because more personal data is being handled * customer trust can erode when requests feel excessive Selective disclosure addresses this by allowing the data exchange to be more precise. ## Business value for the holder [#business-value-for-the-holder] From the holder’s perspective, selective disclosure is valuable because it supports **privacy, convenience, and confidence**. ### Share less personal information [#share-less-personal-information] A holder does not need to reveal their full address, licence number, or date of birth when a verifier only needs confirmation of age or eligibility. ### Keep more control over each interaction [#keep-more-control-over-each-interaction] Selective disclosure gives people a clearer sense of what is being requested and why. That makes the interaction easier to understand and easier to trust. ### Reduce unnecessary exposure [#reduce-unnecessary-exposure] The less personal data that is shared, the less personal data is exposed in each transaction. That can help reduce privacy risk over time. ### Improve digital experience [#improve-digital-experience] When people can share only the needed information, digital interactions can feel more proportionate and less intrusive. ## Business value for the verifier [#business-value-for-the-verifier] Selective disclosure also creates strong business value for the verifier. This is important because privacy is not only a user concern. It is also an operational and compliance concern for the organisation receiving the data. ### Collect only what is needed [#collect-only-what-is-needed] If the verifier only needs proof of age, licence validity, or eligibility, it does not need the rest of the credential. This supports a more focused and efficient verification process. ### Reduce unnecessary data storage [#reduce-unnecessary-data-storage] Collecting more data than necessary creates avoidable obligations. The more personal data an organisation gathers, the more it may need to classify, secure, govern, audit, and delete. ### Lower privacy and security risk [#lower-privacy-and-security-risk] Large stores of personal data can become attractive targets. Selective disclosure helps reduce the chance of building unnecessary data “honeypots” that create extra risk and cost. ### Support data minimisation goals [#support-data-minimisation-goals] Many privacy and compliance programs aim to limit collection to what is necessary for a specific purpose. Selective disclosure supports that principle in a practical way. ### Build trust with customers and partners [#build-trust-with-customers-and-partners] Asking for less data can make an organisation appear more trustworthy and more respectful of user privacy. ## Real-world examples of selective disclosure [#real-world-examples-of-selective-disclosure] Selective disclosure is easiest to understand through concrete examples. ### Age checks [#age-checks] A retailer, venue, or online service may need to know whether a person is old enough for a restricted product or service. In that situation, the verifier may only need: * confirmation that the person is over the required age threshold They may not need: * full date of birth * full address * licence number * other unrelated identity details This creates a more privacy-conscious experience for the holder and reduces unnecessary collection for the verifier. ### Licence validity [#licence-validity] A verifier may need to check whether a person holds a valid licence. For example, they may need to know: * whether the licence is valid * what class of vehicle the person is entitled to drive * whether the licence has expired They may not need to collect: * home address * full identity profile * unrelated credential data This makes the interaction more relevant to the decision at hand. ### Proof of eligibility [#proof-of-eligibility] A person may need to prove they are eligible for a service, discount, entitlement, or benefit. In some cases, the verifier may only need: * confirmation that the person meets the eligibility requirement They may not need full background details about why the person qualifies. This can help reduce oversharing in healthcare, public services, education, workforce, and financial use cases. ## Why this matters for privacy [#why-this-matters-for-privacy] Selective disclosure matters for privacy because it supports a more proportionate way to exchange information. Instead of assuming that every identity check requires a full document, it starts with a simpler question: **What does the verifier actually need to know?** That shift matters because privacy is often lost through routine over-collection, not just through major failures. When organisations repeatedly ask for full documents, users may end up sharing far more information than the transaction requires. Selective disclosure helps reduce that pattern. ## Why this matters for compliance [#why-this-matters-for-compliance] Selective disclosure also matters for compliance and governance. When an organisation collects personal data, it takes on responsibility for handling that data appropriately. Even when collection is lawful, collecting more than needed can increase complexity. A more selective model can help organisations: * align verification with a defined purpose * reduce unnecessary personal data flows * simplify retention and deletion decisions * reduce the scope of data handling controls * support privacy-by-design approaches In other words, asking for less data can mean less risk to manage later. ## Why this matters for trust [#why-this-matters-for-trust] Trust grows when people feel an interaction is fair, transparent, and proportionate. Selective disclosure supports that by helping users see that: * the verifier is only asking for what is needed * the transaction is not based on excessive data collection * the organisation is taking a more privacy-aware approach That can improve confidence in digital services, especially in sectors where identity checks are frequent or sensitive. ## Is selective disclosure only about privacy? [#is-selective-disclosure-only-about-privacy] No. Privacy is a major reason it matters, but selective disclosure also helps with: * **user experience**, by reducing friction * **operational efficiency**, by narrowing the data being handled * **security**, by reducing unnecessary data concentration * **compliance**, by supporting data minimisation * **trust**, by making requests feel more appropriate and transparent It is best understood as a business capability with privacy benefits, not just a technical feature. ## How verifiable digital credentials support selective disclosure [#how-verifiable-digital-credentials-support-selective-disclosure] Verifiable digital credentials make selective disclosure more practical because they are designed for structured, trustworthy data sharing. In the mDocs model, the verifier can request only the information needed, and the holder can agree to share selected information from the credential. This means the verifier does not always need the full credential. Instead, they can receive only the part that matters for the transaction, while still being able to trust what was shared. The technical details behind that process are important, but the business outcome is simpler: * the holder shares less * the verifier collects less * both sides still get a trustworthy result ## Why mobile credentials are especially useful here [#why-mobile-credentials-are-especially-useful-here] Mobile credentials are well suited to selective disclosure because they support interactive digital exchanges. Instead of handing over a full plastic card, the holder can respond to a specific request on their device. That makes it easier to shape the exchange around the purpose of the interaction. This is one reason mobile credentials are increasingly relevant in: * retail and hospitality * government services * financial services * workforce and employment checks * regulated access scenarios Across these use cases, the common value is the same: more precise information sharing. ## What selective disclosure does not mean [#what-selective-disclosure-does-not-mean] Selective disclosure does **not** mean the verifier gets no assurance. It also does **not** mean that every interaction becomes anonymous. Instead, it means the verifier receives the information needed for the decision they are making, without necessarily receiving everything else contained in the credential. That distinction is important. Selective disclosure is about **precision**, not the absence of trust. ## A better model for digital trust [#a-better-model-for-digital-trust] For many organisations, selective disclosure represents a better operating model for digital trust. It allows them to move away from broad document collection and toward more targeted, purpose-specific verification. That can help them: * improve customer experience * reduce unnecessary data handling * lower privacy and security risk * strengthen compliance outcomes * build more trusted digital services For holders, it offers a more respectful and practical way to prove something about themselves without exposing more than necessary. ## Frequently asked questions [#frequently-asked-questions] ### What is selective disclosure? [#what-is-selective-disclosure] Selective disclosure is the ability to share only the information needed for a specific interaction, instead of revealing an entire credential or full set of personal details. For example, a holder can prove they are over a required age without revealing their full date of birth, address, or licence details. ### Why is selective disclosure important? [#why-is-selective-disclosure-important] It is important because it helps reduce unnecessary data sharing. That improves privacy for the holder and reduces data collection, storage, and risk for the verifier. It also supports data minimisation principles in privacy law and reduces the amount of personal data accumulating in systems that could become attractive targets. ### What is an example of selective disclosure? [#what-is-an-example-of-selective-disclosure] A common example is an age check. A verifier may only need confirmation that a person is over a required age, rather than their full date of birth, address, and licence details. Another example is a licence check, where a verifier may only need confirmation that a licence is valid, not the entire licence record. ### How does selective disclosure help businesses? [#how-does-selective-disclosure-help-businesses] It helps businesses collect less unnecessary personal data, reduce privacy and security exposure, support data minimisation, and build trust with customers. Collecting less data also reduces compliance burden under privacy regimes such as the GDPR, the Privacy Act, and similar frameworks. ### How does selective disclosure help users? [#how-does-selective-disclosure-help-users] It helps users avoid oversharing, keep more control over their information, and complete identity checks in a way that feels more proportionate and privacy-aware. People can confirm specific facts about themselves without revealing unrelated personal details. ### Is selective disclosure only relevant for mobile driver’s licences? [#is-selective-disclosure-only-relevant-for-mobile-drivers-licences] No. It is relevant across many verifiable credential use cases, including proof of eligibility, employee credentials, education credentials, health-related credentials, and other digital identity scenarios. ### Does selective disclosure reduce trust in the information being shared? [#does-selective-disclosure-reduce-trust-in-the-information-being-shared] No. The goal is to reduce unnecessary data sharing while still allowing the verifier to trust the information that is disclosed. Each disclosed attribute remains cryptographically signed by the issuer and can be verified to the same standard as the full credential. ## Summary [#summary] Selective disclosure allows a person to share **only the information needed for a specific interaction**. That creates value on both sides: * the **holder** avoids oversharing personal information * the **verifier** avoids collecting data it does not need, reducing risk and compliance burden For organisations building digital services, selective disclosure is not just a privacy feature. It is a practical way to improve trust, reduce data exposure, and create more proportionate verification experiences. # What are verifiable credentials? URL: /docs/concepts/verifiable-credentials ## What is a credential? [#what-is-a-credential] A credential is evidence that proves something about a person. Every day, we use credentials to prove things about ourselves. Our identity, our qualifications, or our right to access certain services. A driver’s licence, a passport, or a student ID - these are all types of credentials. In the real world, we’re familiar with physical credentials such as ID cards, membership/loyalty cards, property/ownership titles and others. Typically we store these credentials in a safe place such as our wallet. This enables us to carry essential personal information and present it physically when needed to prove details about ourselves. For example, showing a driver's license to a police officer to confirm that we are authorised to drive that class of vehicle. ## Digital credentials [#digital-credentials] As more of our everyday interactions shift online, it’s only natural that our credentials follow. People increasingly rely on mobile devices for everything, from banking and travel to accessing government services, and expect the same level of convenience when proving who they are or what they’re entitled to. Digital credentials are the digital counterparts of the physical documents we use every day. They can be stored and presented through digital wallets, which function much like physical wallets but on your mobile device. A digital wallet enables you to carry, manage, and share your credentials electronically. While this brings convenience, simply holding a digital copy of a document is not enough. A scanned ID or a digital “flash pass” might look official, but these copies are just as easy to forge or misuse as a paper document, and sometimes even easier. In many cases, people use screenshots or low-security files to “prove” something about themselves, and these can be edited, duplicated, or faked with minimal effort. As a result, these basic forms of digital credentials cannot be relied upon for trust or security. ## Verifiable credentials [#verifiable-credentials] That’s where verifiable credentials come in. Verifiable credentials are digitally signed, tamper-evident digital statements that can be used to prove things like a person’s identity, age, affiliation, or qualification. They’re designed to be shared, checked, and trusted across both remote and in-person contexts. Unlike static images or scans, verifiable credentials include cryptographic protections that allow others to independently verify their authenticity, ensuring that the credential hasn’t been altered and truly comes from a trusted issuer. They can be presented in a secure, privacy-preserving way and used to streamline interactions where trust is required. Verifiable credentials can represent far more than identification cards. Some examples include: * Mobile Driver's Licenses (mDLs): A Ministry of Transport can issue a digital Mobile Driver’s Licence (mDL) that citizens store in a mobile wallet and present during roadside checks or at rental car counters. * Proof of income: A financial institution might issue a proof-of-income credential that customers can use to apply for loans across different banks. * Educational qualifications: A university can issue digital degree certificates that graduates use to verify their qualifications with employers. Today, when people refer to **digital credentials**, they often mean **verifiable credentials**, but it’s important to be clear about which type is being discussed. A simple digital copy and a cryptographically verifiable credential are very different in terms of security, trust, and reliability. ## Unique features of verifiable credentials [#unique-features-of-verifiable-credentials] ### The cryptographic foundations of trust [#the-cryptographic-foundations-of-trust] Digital credentials, and specifically verifiable digital credentials, are the foundation of the MATTR product stack, enabling organisations and individuals to issue, hold, and verify credentials in a variety of real-world settings, from online services to mobile wallets to physical checkpoints. Verifiable credentials use a fundamentally different trust model from traditional document-based verification, and the difference is easiest to see in how trust is established. With traditional methods, trust is created through a point-in-time check against a central system. For example, an employer may manually review a PDF degree certificate and contact the issuing university to confirm it is valid, or a business may verify a licence by logging into a government portal or using a third-party verification service. These checks rely on indirect signals—such as whether a document number exists or whether a name and date of birth match a database—and assume that the underlying system is accurate and up to date. In practice, these systems can suffer from data gaps, update delays, and human error, making trust inherently probabilistic rather than guaranteed. Verifiable credentials work differently. The proof of authenticity is embedded directly in the credential using cryptography. For example, a digital driver’s licence or university credential can be verified instantly by a trusted verifier without contacting the issuer or querying a central database. A cryptographic signature either matches the issuer’s key or it does not. A credential is either listed as revoked or it is not. The verification result is clear, unambiguous, and does not depend on fuzzy matching, manual review, or backend system availability. This shift reduces manual effort, removes integration silos, and enables trust to move with the data rather than staying locked inside individual systems. It results in greater confidence, clearer outcomes, and a level of assurance that traditional verification alone cannot reliably provide. ### Inverting the trust model [#inverting-the-trust-model] Traditional verification systems depend on checking information with a central authority each time it needs to be verified. For example, a verifier submits a name and document number to a government service and waits for a “yes” or “no” response. This model has two key limitations: * The verifier must trust the authority’s response without being able to independently verify the authenticity of the underlying credential. * Every verification request is visible to the authority, allowing correlation of data checks and concentrating privacy and operational risk in a single system. Verifiable credentials invert this model. Instead of asking a central service to confirm validity, the credential itself carries cryptographic proof of: * Who issued it (issuer authenticity) * What was issued (credential integrity) * To whom it was issued (binding to the holder) * Whether it has been altered, tampered with, or revoked This proof is created using digital signatures based on asymmetric key pairs. A verifier can check the signature directly, without contacting the issuer, using publicly available keys. This results in a stronger and more privacy-preserving form of trust. Trust no longer depends on a live lookup or a third party’s response. Instead, it can be independently and mathematically confirmed by the verifier, making trust deterministic rather than probabilistic. Verifiers do not have to assume that data is genuine. They can cryptographically prove it. ### Trusted issuers [#trusted-issuers] Verifiable credentials still rely on a trusted issuer model, but trust is not based on brand recognition, assumed institutional authority, or static government lists. A verifier does not accept a credential simply because it carries the name of a well-known university, bank, or government agency, nor do they need to look that organisation up in a central registry at verification time. Instead, trust is anchored in cryptographic keys and governed by explicit trust frameworks. Verifiers validate whether a credential was signed with a valid issuer key, whether that key is authorised under the relevant governance model, and whether it ultimately chains back to a recognised trust anchor. This allows trust to be evaluated consistently and independently, rather than inferred from reputation or jurisdiction. Digital credential ecosystems can use hierarchical or mesh trust architectures. In a national system, trust may be anchored in a government root key. In an industry ecosystem—such as health or education—it may be anchored in a consortium or standards body. In cross-border or global use cases, multiple roots of trust can coexist, allowing credentials to be verified across jurisdictions without a single controlling authority. In practice, this means a verifier can accept a digital licence or identity credential without prior knowledge of the issuing organisation. As long as the issuer’s key is trusted within the applicable framework and the signature is valid, the credential can be relied upon. This creates a transparent, portable chain of trust that works consistently across organisations and jurisdictions, without relying on reputation or centralised control. ### Revocation and credential status [#revocation-and-credential-status] Traditional verification typically provides a point-in-time yes/no result, with no artefact that can be independently checked later. For example, a verifier may confirm that an identity document or licence is valid at the moment of lookup, but once that check is complete, there is no portable proof of validity and no way to re-verify the result without repeating the process with the issuing authority. Verifiable credentials work differently. They support built-in lifecycle and privacy controls such as revocation registries, status lists, short-lived credentials, and selective disclosure. In practice, this means a verifier can confirm that a credential is still valid, has not been revoked, and is within its intended lifetime—while the holder shares only the minimum information required, such as proving age eligibility without revealing a full date of birth. Crucially, these checks can be performed without revealing the subject’s identity to the issuer or the wider network. For example, a verifier can confirm that a credential remains valid without notifying the issuing authority that a specific individual is being checked. This reduces correlation and surveillance risk while preserving strong security guarantees. The result is a balanced model where security and privacy reinforce each other, rather than forcing organisations to trade one for the other. ### Integrity and tamper-proof [#integrity-and-tamper-proof] Traditional identity documents—whether physical or digital—can be forged, altered, copied, counterfeited, or manipulated over time. For example, a PDF licence can be edited, a scanned passport can be reused, or a legitimate document can be presented by someone other than its rightful holder. While traditional checks reduce these risks, they do not eliminate them. Spoofing, synthetic identities, and presentation attacks remain persistent challenges, especially at scale. Digital credentials address these weaknesses at the architectural level. Any modification to a digital credential—even changing a single bit—immediately invalidates its cryptographic signature. This makes tampering and forgery detectable by default, rather than something that must be inferred through visual inspection or backend checks. As a result, organisations gain strong assurance at the moment of verification that the data is authentic, unaltered, and issued by a trusted authority. Verification requires no specialist equipment or proprietary systems—only cryptographic validation—making security inherent to the credential rather than dependent on perimeter controls. | Fraud type | Real-world example | How verifiable credentials resist it | | ----------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | Document forgery | A fake university degree created using design tools | The credential cannot be forged without the issuer's private key; invalid signatures are immediately rejected | | Document tampering | Editing a name or expiry date in a PDF licence | Any change invalidates the cryptographic signature, making tampering instantly detectable | | Copy or reuse | Reusing a scanned ID or screenshot across multiple services | Credentials can be bound to the holder and verified using challenge-response, preventing reuse | | Counterfeiting at scale | Mass-produced fake IDs that "look right" | Visual appearance is irrelevant; only valid cryptographic signatures are accepted | | Presentation attacks | Using someone else's legitimate document | Holder binding and selective disclosure ensure the presenter controls the credential | | Insider manipulation | An internal operator alters records in a backend system | Trust is based on signed credentials and governance rules, not mutable databases | | Centralised data breach | Large identity databases compromised and reused | No central store of personal data is required for verification | Digital credentials do not merely reduce fraud risk. They make entire classes of attacks cryptographically infeasible. This gives organisations assurance at the moment of verification that the data is authentic and unaltered. ### Privacy by design [#privacy-by-design] One of the most significant security advantages of digital credentials—often overlooked—is what does not need to be shared. Traditional verification typically requires uploading full documents, exposing more personal data than necessary, and relying on third-party intermediaries to store, process, or validate that information. For example, proving age may involve sharing a complete identity document that reveals a full name, date of birth, address, and document number—far more than the transaction requires. Digital credentials enable selective disclosure. A holder can prove a specific fact—such as being over 18—without revealing their exact birthdate or any unrelated attributes. The verifier receives only what is needed to make a decision, nothing more. This approach significantly reduces data exposure, shrinks the attack surface, limits correlation across transactions, and avoids long-term retention of sensitive personal information. Security is strengthened by minimising what organisations need to collect and hold, not by accumulating more data. ### Empowering the holder [#empowering-the-holder] Unlike traditional methods, where verification is performed on the user, digital credentials place the user at the centre of the interaction. The user holds their credentials, decides when and where to share them, and controls exactly what information is disclosed. Verification happens locally, allowing verifiers to confirm authenticity without broadcasting the user’s activity to central systems or third parties. The result is a model of freedom through trust: strong assurance for organisations, and genuine agency for individuals—where security and empowerment reinforce each other rather than compete. ## Verifiable credentials in production [#verifiable-credentials-in-production] Verifiable credentials are no longer an emerging technology. National and state-level programs are in production or in active rollout across multiple jurisdictions, each making different design choices that reflect its constitutional structure, regulatory tradition, and policy priorities. * **European Union.** All member states are required to provide a citizen-accessible EU Digital Identity Wallet, with private sector acceptance obligations following. The regulatory framework (eIDAS 2.0) and the technical framework (the Architecture and Reference Framework with multiple implementing acts) define federated, supranational standards that each member state implements. * **United Kingdom.** The UK has chosen a single state wallet, the GOV.UK Wallet, with a digital Veteran Card already live and a mobile driving licence in phased public rollout. * **Australia.** Each state runs its own mobile driving licence program aligned with the ISO/IEC 18013-5 standard, and population coverage across the country is high. The federal Digital ID Act 2024 sets a trust framework on top of these state-level credentials. * **United States.** Some state DMVs have launched mobile driving licence programs that allow storage across multiple wallets, including state-issued, Apple Wallet, Google Wallet, and Samsung Wallet, reflecting an explicit multi-wallet choice posture. * **Singapore.** Singpass, a long-established national digital identity used across thousands of agency and business services, is being extended with verifiable credential capabilities on top of its centralized identity foundation. These programs are not converging on a single model. They are converging on a shared technical floor (verifiable credentials, ISO/IEC 18013-5, OID4VP, selective disclosure) and on a shared set of policy questions about how to govern the resulting ecosystem. See [Policy considerations](/docs/concepts/policy-considerations) for those questions and the design choices each one opens up. ## Frequently asked questions [#frequently-asked-questions] ### What is a verifiable credential? [#what-is-a-verifiable-credential] A verifiable credential is a digitally signed, tamper-evident digital statement that can be used to prove things like a person's identity, age, affiliation, or qualification. Unlike static images or scans, verifiable credentials include cryptographic protections that allow others to independently verify their authenticity. ### How are verifiable credentials different from traditional digital documents? [#how-are-verifiable-credentials-different-from-traditional-digital-documents] Traditional digital documents like PDFs or scanned IDs can be easily forged or edited. Verifiable credentials embed cryptographic proof of authenticity directly in the credential, allowing verifiers to confirm validity without contacting the issuer or querying a central database. ### Can verifiable credentials be forged or tampered with? [#can-verifiable-credentials-be-forged-or-tampered-with] No. Any modification to a verifiable credential, even changing a single bit, immediately invalidates its cryptographic signature. This makes tampering and forgery detectable by default, without relying on visual inspection or backend checks. ### What is selective disclosure? [#what-is-selective-disclosure] Selective disclosure allows a credential holder to prove a specific fact, such as being over 18, without revealing their exact birthdate or any unrelated attributes. This reduces data exposure and limits correlation across transactions. ### Who controls a verifiable credential? [#who-controls-a-verifiable-credential] The holder controls their verifiable credentials. They decide when and where to share them, and control exactly what information is disclosed. Verification happens locally, allowing verifiers to confirm authenticity without broadcasting the holder's activity to central systems. # Developer toolbox URL: /docs/resources/dev-toolbox A collection of sample apps, API tools and templates and branding tools that you can use to get started with MATTR VII quickly and easily. ## Developers [#developers] * [Postman collection](/docs/api-reference#postman-collection): Preset configuration to get you going with MATTR VII APIs quickly and easily. * [X.509 certificates validator](https://tools.mattrlabs.com/pem): Decode and validate X.509 certificates. * [CBOR Decoder](https://tools.mattrlabs.com/cbor): Decode CBOR data into a human readable format. * [JWT Decoder](https://tools.mattrlabs.com/jwt): Decode JWT data into a human readable format. ## Issuers [#issuers] * [Pre-authorized Code credential offer generator](https://tools.mattrlabs.com/issue-credential): Generate pre-authorized credential offers for testing. * [Claims studio](https://www.claims.studio/): Create data stores to be used as claim sources when testing your MATTR VII integrations. * [Credential templates](https://github.com/mattrglobal/sample-apps/blob/master/credential-templates/README.md): CWT and Semantic CWT credentials template files. ## Holders [#holders] * [mDoc Online presentation tool](https://tools.mattrlabs.com/mdoc-online-presentation): Test your mDoc presentation capabilities. ## Admins [#admins] * [Custom Domain checker](https://tools.mattrlabs.com/domain-checker): Check whether your custom domain includes the required redirects. ## DTS Operators [#dts-operators] * [VICAL viewer](https://tools.mattrlabs.com/vical-viewer): Display a VICAL .cbor file in a human readable format. Please [reach out](http://mattr.global/contact-us) if you need assistance using any of these tools. # Getting started URL: /docs/resources/get-started Complete the sign-up form to sign up and trial MATTR capabilities. # Resources URL: /docs/resources Resources provide a wealth of information and tools to help you navigate the landscape of digital trust. In this section, you'll find reference documentation, development toolkits, and legal terms that are essential for building and managing secure, interoperable systems. Whether you're a developer, architect, or business leader, these resources are designed to support you at every stage of your journey. Sign up to trial MATTR capabilities API and SDK reference documentation Tools to improve your development experience MATTR Changelogs Supported standards Terms and conditions # Reference Documentation URL: /docs/resources/reference-docs ## MATTR VII [#mattr-vii] * **MATTR VII Platform API**: The core API for managing and interacting with a MATTR VII tenant. * [API Reference](/docs/api-reference): Defines a set of capabilities to manage and interact with a MATTR VII tenant. * [Events Registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html): A comprehensive list of all events generated by the MATTR VII Platform API. * **MATTR VII Management API**: Defines a set of capabilities beyond the scope of a single MATTR VII tenant or environment. * [API Reference](/docs/api-reference/management-api-reference-overview): Defines a set of capabilities beyond the scope of a single MATTR VII tenant or environment. * [Events Registry](https://api-reference-sdk.mattr.global/event-registry-management/latest/index.html): Includes all events generated by the MATTR VII Management API. ## MATTR Pi [#mattr-pi] * **React Native SDKs**: * [mDocs Holder](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html): Build an app for holding, storing and sharing mDocs. * [mDocs Verifier](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html): Build mDocs verification capabilities into new or existing apps. * [Holder](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/latest/index.html): Build an app for holding, storing and sharing verifiable credentials. * [Verifier](https://api-reference-sdk.mattr.global/verifier-sdk-react-native/latest/index.html): Build verification capabilities into new or existing apps. * **iOS Native SDKs**: * [mDocs Holder](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk): Build mDocs claiming and presenting capabilities into your native iOS app. * [mDocs Verifier](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk): Build mDocs verification capabilities into your native iOS app. * **Android Native SDKs**: * [mDocs Holder](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/): Build mDocs claiming and presenting capabilities into your native Android app. * [mDocs Verifier](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/): Build mDocs verification capabilities into your native Android app. * **Web SDKs**: * [Verifier Web](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/index.html): Embed mDocs online verification capabilities into your website. # Android app signing URL: /docs/holding/android-app-signing When registering an Android holder application with your MATTR VII tenant for SDK tethering, you need to provide the `packageSigningCertificateThumbprints` property. This ensures that only your trusted application can interact with the tenant. The Holder SDK uses this configuration to establish a trusted relationship between your Android app and the MATTR VII platform. Without a valid signing certificate thumbprint, the tenant will reject requests from your application — preventing untrusted or modified apps from claiming credentials or performing other holder operations. Every Android app must be signed with a certificate before it can be installed. This signing certificate identifies the developer or organization responsible for the app and is used by Android to verify authenticity and integrity. The package signing certificate thumbprint acts as a unique cryptographic identifier for your app's signing key. By verifying this thumbprint, the MATTR VII tenant can confirm that incoming requests originate from a trusted and unmodified app. The purpose of this page is to explain how to obtain the signing certificate thumbprint for your Android app. ## What is a Package Signing Certificate Thumbprint? [#what-is-a-package-signing-certificate-thumbprint] A package signing certificate thumbprint is the hex-encoded SHA-256 hash of your app's signing certificate (the X.509 certificate bytes). It uniquely identifies the certificate used to sign your app and remains the same across app updates as long as the signing certificate does not change. ## Obtaining the Thumbprint [#obtaining-the-thumbprint] ### Production builds [#production-builds] When your app is ready for release, the thumbprint you configure must match the signing certificate used for your production build. Obtaining the thumbprint differs based on how you manage your signing keys - via Google Play App Signing or manually. #### Google Play App Signing [#google-play-app-signing] If your app uses Play App Signing, Google manages your app's signing key. You can find the SHA-256 fingerprint in the Google Play Console: 1. Go to **Google Play Console** > **Setup** > **App Signing**. 2. Locate the SHA-256 fingerprint under **App Signing Key Certificate**. Google Play Console 3. Copy the SHA-256 value. 4. Remove all `:` characters and convert the string to lowercase. ```js title="Example conversion" const fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5:12:34:56:78:9A:BC:DE:F0:12:34:56:78'; // Remove colons and convert to lowercase const sha256Hex = fingerprint.replaceAll(":", "").toLowerCase(); console.log(sha256Hex) ``` 5. Upload the processed value as your `packageSigningCertificateThumbprints` configuration. For more information, refer to Google's documentation on [Play App Signing Overview](https://developer.android.com/studio/publish/app-signing) and [Manage App Signing Keys in Google Play Console](https://support.google.com/googleplay/android-developer/answer/9842756?hl=en). #### Manual signing (CLI) [#manual-signing-cli] 1. Retrieve the SHA-256 fingerprint using the `keytool` command for the specific signing key alias: ```bash title="Command to get SHA-256 fingerprint" keytool -list -v -keystore -alias ``` 2. Copy the SHA-256 value. 3. Remove all `:` characters and convert the string to lowercase. ```js title="Example conversion" const fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5:12:34:56:78:9A:BC:DE:F0:12:34:56:78:9A:BC:DE:F0:12:34:56:78:9A:BC:DE:F0:12:34:56:78:9A:BC:DE:F0'; const sha256Hex = fingerprint.replaceAll(":", "").toLowerCase(); console.log(sha256Hex) ``` 4. Upload the processed value as your `packageSigningCertificateThumbprints` configuration. ### Local builds (Debug) [#local-builds-debug] 1. Extract the signing certificate information directly from the `.apk` file using the apksigner tool: ```bash title="Command to get SHA-256 fingerprint from APK" apksigner verify --print-certs path/to/your-debug-apk.apk ``` ```nginx title="Example output" Signer #1 certificate DN: C=US, O=Android, CN=Android Debug Signer #1 certificate SHA-256 digest: f59105881315e61502274a499d6efc2d7cc71c5cae266e598290d36b59221f6d // [!code focus] Signer #1 certificate SHA-1 digest: ca09773016ef4db66344ce0dac2827429ea875f1 Signer #1 certificate MD5 digest: c59905769e42c09530898c6dc413258f ``` 2. Copy the SHA-256 digest and upload it as your `packageSigningCertificateThumbprints` configuration. The default debug keystore is usually located at: `$HOME/.android/debug.keystore`. For more details, refer to [Android Debug Signing](https://developer.android.com/studio/publish/app-signing#debug-mode). ## Best practices [#best-practices] * If your app's signing key changes, you'll need to update your application configuration. * Consider removing old thumbprints if you wish to **invalidate older app versions** — for example, after a key rotation or potential compromise. * For development and testing, you can use the debug signing certificate thumbprint, but ensure to switch to the release signing certificate for production builds. * Always keep your signing keys secure and avoid sharing them publicly. # Core capabilities to integrate URL: /docs/holding/core-capabilities The Holder SDK provides three main capability areas that map to the credential lifecycle. This page introduces each one and links to the deeper capability overviews and tutorials. ## Core capabilities to integrate [#core-capabilities-to-integrate] The SDK provides three main capability areas that map to the credential lifecycle. ### Credential claiming [#credential-claiming] Your app receives credential offers (via QR code scan, deep link, or silent push) and claims them using the [OID4VCI protocol](/docs/issuance/oid4vci-overview). **What you implement:** * Handle incoming credential offer URIs (QR scanner, deep link handler, or push notification). * Trigger the SDK's claiming flow with the offer URI. * Display claiming progress and result to the user. * Store credentials securely (handled by the SDK). **Key decisions:** * Which offer channels to support (QR, deep link, push). * How to surface the claiming experience in your app's navigation. * Whether to show credential details before accepting. * Whether your app uses an open model (any compatible wallet can claim) or a restricted model (only your app can claim via custom URI schemes or App Links/Universal Links). This affects whether users can scan QR codes with the device's native camera or must use your in-app scanner. * Whether the issuer requires [wallet attestation](/docs/issuance/credential-issuance/wallet-attestation). If enabled, the Holder SDK proves your app's authenticity before claiming credentials. The SDK handles the attestation proofs for you once [SDK Tethering](/docs/holding/sdk-operations/sdk-tethering) is configured, so no additional wallet backend integration is required. See [claiming credential offers](/docs/issuance/credential-offer/overview#claiming-credential-offers) and the [credential claiming overview](/docs/holding/credential-claiming-overview) for details. Use the [quickstart](/docs/holding/sdk-quickstart) to get started. ### In-person (proximity) presentation [#in-person-proximity-presentation] Your app presents credentials to nearby verifiers over Bluetooth Low Energy, following [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html). **What you implement:** * Display a QR code for the verifier to scan (engagement phase). * Handle the BLE connection and data exchange (managed by SDK). * Show the user what data is being requested and collect consent. * Display presentation result. **Key decisions:** * How to integrate the presentation trigger into your app's UI. * Consent screen design (what data elements to show, how to explain them). * How to handle offline scenarios (no network connectivity). * Whether to prepare for NFC engagement (currently in development). For iOS apps, consider [applying for the Apple NFC entitlement](https://developer.apple.com/support/nfc-se-platform/) early to enable NFC-based engagement when available. See the [proximity presentation overview](/docs/holding/proximity-presentation-overview) and [tutorial](/docs/holding/proximity-presentation-tutorial). ### Remote presentation [#remote-presentation] Your app responds to remote verification requests from web or mobile verifiers using [ISO/IEC 18013-7](https://www.iso.org/standard/91154.html) and [OID4VP](/docs/verification/oid4vp). **What you implement:** * Handle incoming presentation requests (redirect, QR, or Digital Credentials API). * Show the user what data is being requested and collect consent. * Submit the presentation response via the SDK. * Handle same-device and cross-device scenarios. **Key decisions:** * Which invocation methods to support (redirects, QR scanning, Digital Credentials API). * Same-device vs cross-device flow handling. * How to return the user to the requesting app/website after presentation. See the [remote presentation overview](/docs/holding/remote-presentation-overview) and [tutorial](/docs/holding/remote-presentation-tutorial). ## Next steps [#next-steps] Next, see how these capabilities fit together in the [integration architecture](/docs/holding/integration-architecture). # Credential claiming URL: /docs/holding/credential-claiming-overview ## Overview [#overview] Claiming a credential is the process of receiving a verifiable credential into a digital wallet. From the perspective of a holder (and wallet app developer), this is the core capability that enables individuals to collect and manage their digital credentials, such as identity documents, membership cards, or certifications. Where issuers are responsible for creating and signing the credential, the holder’s role is to accept, retrieve, and store the credential securely. Claiming ensures the credential can later be presented to verifiers in a way that is trusted, privacy-preserving, and interoperable. When a user claims a credential, they are: 1. Receiving an offer from an issuer — typically in the form of a QR code, deep link, or push notification. 2. Reviewing details about the issuer and the credential type before deciding to proceed. 3. Authorizing retrieval (through authentication, a pre-authorized code, or other mechanisms depending on the workflow). 4. Requesting and retrieving the credential from the issuer. 5. Storing the credential in their wallet, ready to be presented later to verifiers. ## OID4VCI [#oid4vci] The [OID4VCI specification](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) is an open standard developed by the OpenID Foundation, defining how digital wallets can receive verifiable credentials from credential issuers in a secure and interoperable way. It leverages the widely adopted OAuth 2.0 and OpenID Connect to establish a trust framework for credential issuance, ensuring both privacy and user control. If you are unfamiliar with OpenID Connect, the identity protocol underpinning the OpenID provisioning capability, there are many excellent guides available online such as this guide from [Google](https://developers.google.com/identity/openid-connect/openid-connect), or this guide from [Mozilla](https://infosec.mozilla.org/guidelines/iam/openid_connect.html). ## Workflows [#workflows] OID4VCI defines two distinct workflows, each tailored to different use cases and requirements: * [Authorization Code flow](/docs/issuance/authorization-code/overview): This interactive, user-driven flow requires the credential recipient (typically a wallet) to redirect the user to the issuer (such as a government or organization) for authentication. After the user successfully authenticates and gives consent, the issuer's authentication provider returns an authorization code. The wallet then exchanges this code for an access token, which is used to obtain the credential. The following credential formats can be claimed via the Authorization Code flow: * [CWT](/docs/concepts/cwt) * [Semantic CWT](/docs/concepts/cwt) * [mDocs](/docs/concepts/mdocs) * [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview): In this flow, the issuer prepares the credential issuance in advance and may authenticate and authorize the holder ahead of time. Instead of obtaining an authorization code through user authentication, the wallet receives a pre-authorized code directly from the issuer, often via an out-of-band method. The user does not need to authenticate again and the wallet presents the pre-authorized code to retrieve an access token and then claim the credential. For added security, the issuer can require a transaction code (shared separately with the holder) which the wallet must also provide to claim the credential. The Pre-authorized Code flow is only supported for [mDocs](/docs/concepts/mdocs). MATTR VII supports both workflows, allowing you to choose the one that best fits your use case. # Learn how to build an application that can claim an mDoc via OID4VCI URL: /docs/holding/credential-claiming-tutorial Coming soon... ## Introduction [#introduction] In this tutorial, you will learn how to use the [mDocs Holder SDKs](/docs/holding/sdk-overview) to build an application that can claim an [mDoc](/docs/concepts/mdocs) issued via both the OID4VCI [Authorization Code](/docs/issuance/authorization-code/overview) and [Pre-authorized Code](/docs/issuance/pre-authorized-code/overview) flows. OID4VCI Authorization Code Tutorial Workflow 1. The user launches the application and scans a QR code received from an issuer. 2. The application displays what credential is being offered to the user and by what issuer. 3. The user agrees to claiming the offered credential. 4. The user is redirected to complete authentication (Only in the Authorization Code flow). 5. Upon successful authentication, the credential is issued to the user's application, where they can now store, view and present it. The result will look something like this: ## Prerequisites [#prerequisites] Before you get started, let's make sure you have everything you need. ### Prior knowledge [#prior-knowledge] * The issuance workflow described in this tutorial is based on the [OID4VCI](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) specification. If you are unfamiliar with this specification, refer to the following resources for more information: * What is [credential issuance](/docs/issuance)? * Breakdown of the [OID4VCI workflow](/docs/issuance/oid4vci-overview). * Understand the difference between the [Authorization Code](/docs/issuance/authorization-code/overview) and [Pre-authorized Code](/docs/issuance/pre-authorized-code/overview) flows. * What are [mDocs](/docs/concepts/mdocs)? * We assume you have experience developing applications in the relevant programming languages and frameworks (Swift for iOS, Kotlin for Android and TypeScript for React Native). If you need to get a holding solution up and running quickly with minimal development resources and in-house domain expertise, [talk to us](http://mattr.global/contact-us) about our white-label [MATTR GO Hold app](https://mattr.global/platforms/go) which might be a good fit for you. ### Assets [#assets] * As part of your onboarding process you should have been provided with access to the following assets ([Contact us](https://mattr.global/contact-us) if you are interested in trialing the SDK): * ZIP file which includes the required framework: (`MobileCredentialHolderSDK-*version*.xcframework.zip`). * Sample Wallet app: You can use this app for reference as you work through this tutorial. This tutorial is only meant to be used with the most [recent version](/docs/holding/sdk-overview#versions) of the iOS mDocs Holder SDK. * As part of your onboarding process you should have been provided with access to the following assets ([Contact us](https://mattr.global/contact-us) if you are interested in trialing the SDK): * ZIP file that includes the required library (`mobile-credential-holder-*version*.zip`). * Sample Wallet app: You can use this app for reference as you work through this tutorial. This tutorial is only meant to be used with the most [recent version](/docs/holding/sdk-overview#versions) of the Android mDocs Holder SDK. * You will need access to the SDK and additional MATTR dependencies to complete this tutorial. [Contact us](https://mattr.global/contact-us) if you are interested in trialing the SDK. This tutorial is only meant to be used with the most [recent version](/docs/holding/sdk-overview#versions) of the React Native mDocs Holder SDK. ### Development environment [#development-environment] * [Xcode](https://developer.apple.com/xcode/) setup with either: * Local build settings if you are developing locally. * [iOS developer account](https://developer.apple.com/programs/enroll/) if you intend to publish your app. * [Android Studio](https://developer.android.com/studio/). * Code editor (such as [VS Code](https://code.visualstudio.com/download)). * [Android Studio](https://developer.android.com/studio/). * [Xcode](https://developer.apple.com/xcode/). * [yarn](https://yarnpkg.com/) (v1.22.22 was used during development). * Java v17. This tutorial uses [Expo Go](https://expo.dev/go), leveraging [Development Builds](https://docs.expo.dev/develop/development-builds/introduction/). **Dependencies** All dependencies are included in the project's `package.json` file and are automatically installed as part of the [environment setup](#install-the-dependencies). `@mattrglobal/mobile-credential-holder-react-native` **Additional core and utility dependencies:** * `expo-build-properties` * `expo-router` (including `react-native-safe-area-context` `react-native-screens` `expo-linking` `expo-constants` `expo-status-bar`) - used for file-based routing. * `react-native-vision-camera` - handles camera permissions and QR code scanning for interacting with QR code credential offers. * `expo-web-browser` - used to open the authorization URL. * `react-native-qrcode-svg` - used for the future proximity presentation tutorial. ### Testing device [#testing-device] * Supported iOS device to run the built application on, setup with: * Biometric authentication (Face ID, Touch ID). * Available internet connection. * Supported Android device to run the built application on, setup with: * Biometric authentication (Face recognition, fingerprint recognition). * Available internet connection. * [Debugging](https://developer.android.com/studio/debug/dev-options#enable) enabled. * Supported iOS and/or Android device to run the built application on, setup with: * Available internet connection. * iOS: * Biometric authentication (Face ID, Touch ID). * Android: * Biometric authentication (Face recognition, fingerprint recognition). * [Debugging](https://developer.android.com/studio/debug/dev-options#enable) enabled. Got everything? Let's get going! ## Environment setup [#environment-setup] Perform the following steps to setup and configure your development environment: **Step 1: Create a new project** Please follow the detailed instructions to [Create a new Xcode project](https://help.apple.com/xcode/mac/current/#/dev07db0e578) and add your organization’s identifier. Create a new project **Step 2: Unzip the dependencies file** 1. Unzip the `MobileCredentialHolderSDK-*version*.xcframework.zip` file. 2. Drag the `MobileCredentialHolderSDK-*version*.xcframework` folder into your project. 3. Configure `MobileCredentialHolderSDK.xcframework` to [Embed & sign](https://help.apple.com/xcode/mac/current/#/dev51a648b07). See [Add existing files and folders](https://help.apple.com/xcode/mac/current/#/dev81ce1d383) for detailed instructions. This should result in the the following framework being added to your project: Framework added **Step 3: Configure required resources** 1. [Create a new file](https://developer.apple.com/documentation/xcode/managing-files-and-folders-in-your-xcode-project) named `Constants.swift` within your project. 2. [Add the following string resources](https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package) to represent the Authentication provider you will use for this tutorial: ```swift title="Constants.swift" enum Constants { static let redirectUri: String = "io.mattrlabs.sample.mobilecredentialholderapp://credentials/callback" static let clientId: String = "ios-sample-mobile-credential-holder-app" } ``` * `redirectUri` : This is the path the SDK will redirect to once the user completes Authentication with the issuer. Our best practice recommendation is to configure this to be `{redirect.scheme}://credentials/callback` as shown in the example above. However, it can be any path as long as it is handled by your application and registered with the issuer against the corresponding `clientId`. * `clientId` : This is the identifier that is used by the issuer to recognize the wallet application. This is only used internally in the interaction between the wallet and the issuer and can be any string as long as it is registered with the issuer as a trusted wallet application. Both of these parameters are only used in the Authorization Code flow and must be registered as a key pair as part of the **issuer's** OID4VCI workflow configuration. In this tutorial you will be claiming a credential from a MATTR Labs issuer which is configured with the parameters detailed above. We will help you configure your unique values as you move your implementation into production. **Step 4: Add Bluetooth and biometric permissions** The SDK requires access to the mobile device Bluetooth and biometric capabilities for the different workflows built in this tutorial. [Configure these permissions](https://help.apple.com/xcode/mac/current/#/dev37c2f42ff) in the `Info` tab of the Application target: Privacy capabilities **Step 5: Run the application** Select **Run** and make sure the application launches with a *“Hello, world!”* text in the middle of the display, as shown in the following image:
Application ready
**Step 1: Create a new project** 1. [Create a new Android Studio project](https://developer.android.com/studio/projects/create-project), using the *Empty Activity* template. Create a new Android project 2. Name the project `Holder Tutorial`. 3. Select *API 24* as the `Minimum SDK` version. 4. Select *Kotlin DSL* as the `Build configuration language`. Project configuration 5. Select **Finish**. 6. [Sync](https://developer.android.com/build#sync-files) the project with Gradle files. **Step 2: Add required dependencies** 1. Select the [Project view](https://developer.android.com/studio/projects#ProjectView). Project view 2. Create a new directory named `repo` in your project's folder. 3. Unzip the `mobile-credential-holder-*version*.zip` file and copy the unzipped `global` folder into the new `repo` folder. Unzipped files copied 4. Open the `settings.gradle.kts` file in the `HolderTutorial` folder and add the following Maven repository to the `dependencyResolutionManagement.repositories` block: ```kotlin title="settings.gradle.kts" maven { url = uri("repo") } ``` 5. Open the `app/build.gradle.kts` file in your `app` folder and add the following dependencies to the `dependencies` block: ```kotlin title="app/build.gradle.kts" implementation("global.mattr.mobilecredential:holder:7.0.0") implementation("androidx.navigation:navigation-compose:2.9.0") ``` * The `holder` dependency version should match the version of the unzipped `mobile-credential-holder-*version*.zip` file you copied to the `repo` folder. * The required `navigation-compose` version may differ based on your version of the IDE, Gradle, and other project dependencies. 6. In your app's `AndroidManifest.xml` file, add the following `activity` declaration. This represents the [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) you will use for this tutorial: ```xml title="AndroidManifest.xml" ``` These values are part of the `redirect URI` the SDK will redirect the user to once they complete Authentication with the issuer: * `host` : The `host` can be any path, but our best practice recommendation is to configure this to be `credentials`, as the standard format for the `redirect URI` is `{redirect.scheme}://credentials/callback`. * `scheme` : The `scheme` can be any path that is handled by your application and registered with the issuer. These values (alongside the client ID, which will be discussed later) are only used in the Authorization Code flow and must be registered as part of the **issuer's** OID4VCI workflow `redirect URI`. In this tutorial you will be claiming a credential from a MATTR Labs issuer which is already configured with the parameters detailed above. We will help you configure your unique values as you move your implementation into production. 7. [Sync](https://developer.android.com/build#sync-files) the project with Gradle files. 8. Open the [Build](https://developer.android.com/studio/run#gradle-console) tab and select `Sync` to make sure that the project has synced successfully. Synced successfully **Step 3: Run the application** 1. Connect a [debuggable](https://developer.android.com/studio/debug/dev-options#Enable-debugging) Android mobile device to your machine. 2. [Build and run the app](https://developer.android.com/studio/run) on the connected mobile device. The app should launch with a *“Hello, Android!”* text displayed. **Step 1: Access the tutorial codebase** 1. Access the tutorial starter codebase by either: * Cloning the MATTR sample apps repository: ```bash title="Clone the repository" git clone https://github.com/mattrglobal/sample-apps.git ``` or * Downloading the starter directory using the [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Freact-native-mdocs-holder-tutorial%2Freact-native-mdocs-holder-tutorial-starter) utility. 2. Open the tutorial project in your code editor. You can find it in the sample-apps/react-native-mdocs-holder-tutorial/react-native-mdocs-holder-tutorial-starter/ directory. When cloning the entire sample apps repository, you can find the completed tutorial code in the `sample-apps/react-native-mdocs-holder-tutorial/react-native-mdocs-holder-tutorial-complete` directory and use it as a reference. **Step 2: iOS Application configuration** 1. Open the `app.config.ts` file and update the `bundleIdentifier` value under the `// Update the bundle identifier` comment to a unique value for your application, e.g. `com.mycompany.myapp`. ```typescript title="app.config.ts" bundleIdentifier: "com.mycompany.myapp", ``` iOS requires each app to have a unique bundle identifier for App Store and development environments. 2. Add the following Face ID (`NSFaceIDUsageDescription`) and camera usage (`NSCameraUsageDescription`) permissions to the `ios.infoPlist` object under the `// Add Face ID and Camera usage permissions` comment: ```ts title="app.config.ts" NSFaceIDUsageDescription: "Face ID is used to secure your credentials.", NSCameraUsageDescription: "Camera is used to scan QR codes.", ``` These permissions are required for the SDK to use biometric authentication and the camera for QR code scanning. 3. Add the following code under the `// Add Bluetooth permissions.` comment in the `app.config.ts` file to add Bluetooth permissions to the application: ```ts title="app.config.ts" NSBluetoothAlwaysUsageDescription: "This app uses Bluetooth to communicate with verifiers or holders.", NSBluetoothPeripheralUsageDescription: "This app uses Bluetooth to communicate with verifiers or holders.", ``` These permissions will be required for the SDK to use Bluetooth for device engagement and communication in the [proximity presentation tutorial](/docs/holding/proximity-presentation-tutorial). **Step 3: Configure the SDK plugins** Add the following code under the `// Configure the SDK plugins` comment to import required plugin configurations: ```ts title="app.config.ts" "./withAndroidHolderSDK.js", "./withRemoveAPNSCapability.js", ``` The SDK requires platform-specific configurations to work correctly. The plugin files have already been created in your project root directory. You can also follow the instructions in the [mDocs Holder](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html#md:install-dependencies) SDK Docs to perform these platform-specific configuration manually. **Step 4: Install the dependencies** 1. Open a terminal in the project's root and navigate to the starter project directory: ```bash title="Navigate to the project starter directory" cd sample-apps/react-native-mdocs-holder-tutorial/react-native-mdocs-holder-tutorial-starter/ ``` 2. Install the application [dependencies](#dependencies): ```bash title="Install dependencies" yarn install ``` **Step 5: Generate the iOS and Android project files** Run the following command to generate the iOS and Android project files: ```bash title="Generate project files" yarn expo prebuild ``` You should now see the `ios` and `android` folders in your project root. **Step 6: Start the application** Connect your testing device(s) and run the following command to start the application(s): **iOS** ```bash title="Start iOS application" yarn ios --device ``` **Android** ```bash title="Start Android application" yarn android --device ```
Nice work, your application is now all set to begin using the SDK! ## Tutorial steps [#tutorial-steps] In this part of the tutorial you will build the capabilities for the user to interact with an OID4VCI [credential offer](/docs/issuance/credential-offer/overview) and claim an [mDoc](/docs/concepts/mdocs). To achieve this, you will break this capability down into the following steps: 1. Initialize the SDK. 2. Interact with a Credential offer. 3. Retrieve offer details and present them to the Holder. 4. Obtain user consent and initiate Credential issuance. ### Step 1: Initialize the SDK [#step-1-initialize-the-sdk] The first capability you will build into your app is to initialize the SDK so that your app can use SDK methods and classes. To achieve this, you need to import the `MobilecredentialHolderSDK` framework and then initialize the `MobileCredentialHolder` class: 1. Open the `ContentView` class in your new app project and replace any existing code with the following: ```swift title="ContentView" import SwiftUI // Claim Credential - Step 1.2: Import MobileCredentialHolderSDK struct ContentView: View { @State var viewModel: ViewModel = ViewModel() var body: some View { NavigationStack(path: $viewModel.navigationPath) { VStack { Button("Claim Credential") { viewModel.navigationPath.append(NavigationState.qrScan) } .padding() createQRCodeButton if viewModel.shouldDisplayOnlinePresentation { Button("View Online Presentation Session") { viewModel.navigationPath.append(NavigationState.onlinePresentation) } .padding() } Spacer() } .padding() .navigationDestination(for: NavigationState.self) { destination in switch destination { case .qrScan: codeScannerView case .credentialOffer: credentialOfferView case .transactionCodeInput: transactionCodeInputView case .retrievedCredentials: retrievedCredentialsView case .onlinePresentation: // Online Presentation - Step 3.3: Display online presentation view EmptyView() case .presentCredentials: qrCodeView case .proximityPresentation: // Proximity Presentation - Step 2.5: Display proximity presentation view EmptyView() } } // Online Presentation - Step 2.4: Create session from request URI } // Claim Credential - Step 1.4: Initialize the SDK when the view appears .task { await viewModel.initialize() } } // MARK: - Credential Retrieval Views var codeScannerView: some View { // Claim Credential - Step 2.4 Create QRScannerView EmptyView() } var credentialOfferView: some View { // Claim Credential - Step 3.5: Display Credential offer EmptyView() } var transactionCodeInputView: some View { // Claim Credential - Step: 3.4 Display transaction code input view. EmptyView() } var retrievedCredentialsView: some View { // Claim Credential - Step 4.4: Display retrieved credentials EmptyView() } // MARK: - Proximity Presentation Views var createQRCodeButton: some View { // Proximity Presentation - Step 1.5: Add button to generate QR code EmptyView() } func generateQRCode(data: Data) -> Data? { // Proximity Presentation - Step 1.6: Generate QR code return nil } var qrCodeView: some View { // Proximity Presentation - Step 1.7: Create QR code view EmptyView() } } @Observable class ViewModel { var navigationPath = NavigationPath() // Claim Credential - Step 1.3: Add MobileCredentialHolder var // Claim Credential - Step 3.1: Add DiscoveredCredentialOffer and discoveredCredentialOfferURL vars // Claim Credential - Step 4.1: Add retrievedCredentials var // Proximity Presentation - Step 1.2: Create deviceEngagementString and proximityPresentationSession variables // Proximity and Online Presentation: Create variables for credential presentations // Online Presentation - Step 2.1: Create a variable to hold the online presentation session object var shouldDisplayOnlinePresentation: Bool { // Online Presentation - Step 3.4: View Online Presentation return false } // Claim Credential - Step 1.4: Initialize MobileCredentialHolder SDK @MainActor func getCredential(id: String) { // Proximity and Online Presentation: Retrieve a credential from storage print("This method will get a credential from storage and update the viewModel") } } // MARK: - Credential Retrieval extension ViewModel { @MainActor func discoverCredentialOffer(_ offer: String) { // Claim Credential - Step 3.2: Add discover credential offer logic print("This method will discover a credentials offer and update viewModel") } @MainActor func retrieveCredential(transactionCode: String?) { // Claim Credential - Step 4.2: Call retrieveCredential method print("This method will save a credential from offer and store it in the application's storage ") } } // MARK: - Online Presentation extension ViewModel { @MainActor func createOnlinePresentationSession(authorizationRequestURI: String) async { // Online Presentation - Step 2.3: Create online presentation session print("This method will create an online presentation session and update viewModel") } @MainActor func sendOnlinePresentationSessionResponse(id: String) { // Online Presentation - Step 4.1: Send online presentation response print("This method will be passed to a view and send a response with selected credentials") } } // MARK: - Proximity Presentation extension ViewModel { func createDeviceEngagementString() { // Proximity Presentation - Step 1.3: Create function to create a proximity presentation session and generate QR code print("This method will create a device engagement string that will be converted to a QR code") } // Proximity Presentation - Step 1.4: Update function signature func onRequestReceived() { // Proximity Presentation - Step 2.2: Store credential requests and matched credentials print("The signature of this method will need to be updated to include the correct parameters") print("This is a method that will be called when a proximity presentation request is received") } @MainActor func sendProximityPresentationResponse(id: String) { // Proximity Presentation - Step 3.1: Send a credential response print("This method will be passed to a view and send a response with selected credentials") } } // MARK: - Navigation enum NavigationState: Hashable { case qrScan case credentialOffer case transactionCodeInput case retrievedCredentials case onlinePresentation case presentCredentials case proximityPresentation } ``` This will serve as the basic structure for your application for this and the future tutorials. We will copy and paste different code snippets into specific locations to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step (e.g. `// Claim Credential - Step 1.2: Import MobileCredentialHolderSDK`). We recommend copying and pasting the comment text to easily locate it in the code. 2. Add the following code after the `// Claim Credential - Step 1.2: Import MobileCredentialHolderSDK` comment to import `MobileCredentialHolderSDK` and enable using its capabilities in your application: ```swift title="ContentView" import MobileCredentialHolderSDK ``` 3. Add the following code after the `// Claim Credential - Step 1.3: Add MobileCredentialHolder var` comment to create a variable that holds the `mobileCredentialHolder` instance: ```swift title="ContentView" var mobileCredentialHolder: MobileCredentialHolder ``` 4. Add the following code after the `// Claim Credential - Step 1.4: Initialize MobileCredentialHolder SDK` comment. As of iOS Holder SDK v6.0.0, `initialize` is asynchronous, so we keep the `init()` synchronous (assigning the shared instance only) and perform initialization in an `async` method. We call this method from a [`.task`](https://developer.apple.com/documentation/swiftui/view/task\(id:name:executorpreference:priority:file:line:_:\)) modifier on the view in the next step, so it runs once when the view appears: ```swift title="ContentView" init() { mobileCredentialHolder = MobileCredentialHolder.shared } @MainActor func initialize() async { do { try await mobileCredentialHolder.initialize( userAuthenticationConfiguration: UserAuthenticationConfiguration(userAuthenticationBehavior: .onDeviceKeyAccess), credentialIssuanceConfiguration: CredentialIssuanceConfiguration( redirectUri: Constants.redirectUri, autoTrustMobileCredentialIaca: true ) ) } catch { print(error) } } ``` Let's review the parameters that are passed into `initialize`: * `userAuthenticationConfiguration`: Defines when is user authentication required. In this example, authentication will only be required when a credential is issued and/or presented. Refer to the [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/userauthenticationbehavior) to see all options. * `CredentialIssuanceConfiguration` : * `redirectUri`: This is the URI that the SDK uses to redirect the user back to your wallet application after authentication is complete. It must match the value you configured earlier in the [development environment setup](#configure-required-resources). This value is only used and required in the Authorization Code flow. * `autoTrustMobileCredentialIaca`: Controls how the SDK handles issuer IACA certificates during credential issuance. * If set to `true`, the SDK will automatically download and trust the issuer’s IACA certificate(s) when claiming a credential. This allows credentials to be claimed from any issuer. * If set to `false`, the SDK will only accept credentials from issuers whose IACA certificates have already been manually added to the SDK’s trusted issuers list (see [addTrustedIssuerCertificates](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/addtrustedissuercertificates\(certificates:\))). This requires the application to manually manage IACA certificates. 5. [Run](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device) the app to make sure it compiles properly. The first capability you will build into your app is to initialize the SDK so that your app can use SDK functions and classes. To achieve this, you need to initialize the `MobileCredentialHolder` class: 1. Open the `MainActivity` file in your project and replace any existing code with the following: ```kotlin title="MainActivity.kt" package com.example.holdertutorial import android.app.Activity import android.content.Intent import android.os.Bundle import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navDeepLink import com.example.holdertutorial.ui.theme.HolderTutorialTheme import global.mattr.mobilecredential.holder.dto.MobileCredential import global.mattr.mobilecredential.holder.MobileCredentialHolder import global.mattr.mobilecredential.holder.ProximityPresentationSession import global.mattr.mobilecredential.holder.issuance.CredentialIssuanceConfiguration import global.mattr.mobilecredential.holder.issuance.dto.DiscoveredCredentialOffer import global.mattr.mobilecredential.holder.issuance.dto.RetrieveCredentialResult import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() // Claim Credential - Step 1.2: Initialize the SDK setContent { HolderTutorialTheme { val navController = rememberNavController() Scaffold { innerPadding -> NavHost( modifier = Modifier .fillMaxSize() .padding(innerPadding) .padding(8.dp), startDestination = "home", navController = navController, ) { composable("home") { HomeScreen(this@MainActivity, navController) } composable("scanOffer") { // Claim Credential - Step 2.5: Add "Scan Offer" screen call } composable("retrievedCredential") { // Claim Credential - Step 4.9: Add "Retrieved Credential" screen call } composable("presentationQr") { // Proximity Presentation - Step 1.2: Add "QR Presentation" screen call } composable("presentationSelectCredentials") { // Proximity Presentation - Step 2.6: Add "Select Credential" screen call } // Online Presentation - Step 2.2: Add "Online Presentation" screen call } } } } } } @Composable fun HomeScreen(activity: Activity, navController: NavController) { val coroutineScope = rememberCoroutineScope() var transactionCode by remember { mutableStateOf("") } Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Button(onClick = { navController.navigate("scanOffer") }, Modifier.fillMaxWidth()) { Text("Claim Credential") } // Proximity Presentation - Step 1.3: Add button for starting the credentials presentation workflow // Claim Credential - Step 3.3: Display discovered credential offer } } // Claim Credential - Step 4.4: Create function to retrieve credentials object SharedData { // Claim Credential - Step 3.1: Add discovered credential offer variables // Claim Credential - Step 4.2: Add retrieved credentials variable // Proximity Presentation - Step 2.1: Add proximity presentation request variable } ``` This will serve as the basic structure for your application. We will copy and paste different code snippets into specific locations in this codebase to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step. We recommend leaving the comment text (e.g. `// Claim Credential - Step 1.2: Initialize the SDK`) even after you have pasted the code snippet, as it will later help you to easily locate the step in the code. 2. Add the following code after the `// Claim Credential - Step 1.2: Initialize the SDK` comment to initialize the SDK by creating a new instance of the `MobileCredentialHolder` class: ```kotlin title="MainActivity.kt" lifecycleScope.launch { try { MobileCredentialHolder.getInstance().initialize( context = this@MainActivity, // Step 4.1: Add credential issuance configuration ) } catch (e: Exception) { Log.e("MainActivity", "SDK initialization failed", e) } } ``` This will initialize the SDK, making it available for your application. 3. [Run](https://developer.android.com/studio/run#basic-build-run) the app to make sure it compiles properly. The first capability you will build is to initialize the SDK so that your app can use its methods and classes. To achieve this you will create a React Context that will allow accessing an SDK instance and its helper functions throughout the application. 1. In your project's `providers` directory, create a new file named `HolderProvider.tsx` and add the following scaffolding code: ```ts title="/providers/HolderProvider.tsx" import { type MobileCredentialMetadata, UserAuthenticationBehavior, UserAuthenticationType, deleteCredential, getCredentials, initialize, isInitialized, } from "@mattrglobal/mobile-credential-holder-react-native"; // Online Presentation - Step 2.3: Import expo-linking and expo-router import type React from "react"; import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; import { Alert } from "react-native"; type HolderContextProps = { isHolderInitialized: boolean; getMobileCredentials: () => Promise; mobileCredentials: MobileCredentialMetadata[]; deleteMobileCredential: (credentialId: string) => Promise; error: string | null; isLoading: boolean; }; const HolderContext = createContext( undefined, ); export function HolderProvider({ children }: { children: React.ReactNode }) { const [isHolderInitialized, setIsHolderInitialized] = useState(false); const [mobileCredentials, setMobileCredentials] = useState< MobileCredentialMetadata[] >([]); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(true); // Online Presentation - Step 2.4: Initialize router variable // Claim a Credential - Step 1.2: Initialize the Holder SDK const getMobileCredentials = useCallback(async () => { if (!isHolderInitialized) return; const credentials = await getCredentials(); setMobileCredentials(credentials); }, [isHolderInitialized]); // When the holder is initialized, get the mobile credentials to display in the app useEffect(() => { if (isHolderInitialized) { getMobileCredentials(); } }, [isHolderInitialized, getMobileCredentials]); // An example implementation of deleting a credential, used for demonstration purposes const deleteMobileCredential = useCallback( async (credentialId: string) => { if (!isHolderInitialized) return; Alert.alert( "Confirm Deletion", "Are you sure you want to delete this credential?", [ { text: "Cancel", style: "cancel" }, { text: "Delete", style: "destructive", onPress: async () => { try { await deleteCredential(credentialId); Alert.alert("Success", "Credential deleted successfully."); await getMobileCredentials(); } catch (err) { console.error("Error deleting credential:", err); Alert.alert( "Error", err instanceof Error ? err.message : "Failed to delete credential.", ); } }, }, ], ); }, [isHolderInitialized, getMobileCredentials], ); // Online Presentation - Step 2.5: Handle deep link const contextValue = useMemo( () => ({ isHolderInitialized, error, isLoading, getMobileCredentials, mobileCredentials, deleteMobileCredential, }), [ isHolderInitialized, error, isLoading, getMobileCredentials, mobileCredentials, deleteMobileCredential, ], ); return ( {children} ); } export function useHolder() { const context = useContext(HolderContext); if (context === undefined) { throw new Error("useHolder must be used within a HolderProvider"); } return context; } ``` This will serve as the basic structure for your application. You will copy and paste different code snippets into specific locations in this codebase to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step number. We recommend copying and pasting the comment's text (e.g. `// Claim a Credential - Step 1.2: Initialize the Holder`) to easily locate it in the code. 2. Add the following code under the `// Claim a Credential - Step 1.2: Initialize the Holder SDK` comment to call the SDK's [`initialize`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/initialize.html) method and initialize the SDK: ```ts title="/providers/HolderProvider.tsx" const initializeHolder = useCallback(async () => { try { // Check if SDK is already initialized const alreadyInitialized = await isInitialized(); if (alreadyInitialized) { setIsHolderInitialized(true); return; } // Initialize the SDK with authentication and credential issuance configuration const result = await initialize({ userAuthenticationConfiguration: { userAuthenticationBehavior: UserAuthenticationBehavior.OnInitialize, userAuthenticationType: UserAuthenticationType.BiometricOrPasscode, }, credentialIssuanceConfiguration: { autoTrustMobileCredentialIaca: true, redirectUri: "io.mattrlabs.sample.reactnativemobilecredentialholdertutorialapp://credentials/callback" } }); if (result.isErr()) { setError(result.error.message || "Failed to initialize holder."); } else { setIsHolderInitialized(true); } } catch (err) { setError( err instanceof Error ? err.message : "Unknown error during initialization" ); } finally { setIsLoading(false); } }, []); useEffect(() => { initializeHolder(); }, [initializeHolder]); ``` 3. Open the `/app/_layout.tsx` file and replace its content with the following code to wrap the application with the `HolderProvider` context: ```ts title="/app/_layout.tsx" import { HolderProvider } from "@/providers/HolderProvider"; import { Stack } from "expo-router"; export default function RootLayout() { return ( // Claim a Credential - Step 1.3: Wrap the app in the HolderProvider component to make the HolderContext available to any child components ); } ``` Your application can now use the SDK's methods and classes. Next, you will build the capability to interact with a Credential offer. The application will now require biometric authentication whenever the SDK is initialized. ### Step 2: Interact with a Credential offer [#step-2-interact-with-a-credential-offer] Interact with Credential offer Users can receive OID4VCI Credential offers as deep-links or QR codes. In this tutorial you will use a MATTR Labs OID4VCI [Credential offer](/docs/issuance/credential-offer/overview) rendered as a QR code. Creating your own Credential offer is not within the scope of the current tutorial. You can follow the [OID4VCI guide](/docs/issuance/credential-offer/guide) that will walk you through creating one. Your application needs to let users interact with Credential offers. Since this tutorial uses a QR code to deliver the offer, your application must be able to scan and process QR codes. For ease of implementation, you will use a third party framework to achieve this: 1. Add [camera usage permissions](https://help.apple.com/xcode/mac/current/#/dev37c2f42ff) to the app target: Camera permissions 2. Add the [CodeScanner](https://github.com/twostraws/CodeScanner) library via [Swift Package Manager](https://help.apple.com/xcode/mac/current/#/devb83d64851). Code scanner package 3. [Create a new swift file](https://help.apple.com/xcode/mac/current/#/dev81ce1d383) named `QRScannerView` and add the following code into it to implement the QR scanning capability: ```swift title="QRScannerView" import SwiftUI import CodeScanner import AVFoundation struct QRScannerView: View { private let completionHandler: (String) -> Void init(completion: @escaping (String) -> Void) { completionHandler = completion } var body: some View { CodeScannerView(codeTypes: [.qr]) { result in switch result { case .failure(let error): print(error.localizedDescription) case .success(let result): print(result.string) completionHandler(result.string) } } } } ``` 4. Return to the `ContentView` file and replace the `EmptyView()` under the `// Claim Credential - Step 2.4 Create QRScannerView` comment with the following code to create a new `QRScannerView` view in the application for scanning QR codes: ```swift title="ContentView" QRScannerView( completion: { credentialOffer in viewModel.discoverCredentialOffer(credentialOffer) } ) ``` 5. [Run](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device) the app and tap the **Claim Credential** button. When prompted, grant camera access to allow QR code scanning. You should see a result similar to the following: As the user selects the **Claim Credential** button, the app launches the device camera to enable the user to scan a QR code. You might notice that nothing happens after scanning a QR code - this is expected. In the next step you will implement the logic that retrieves the credential offer details from the QR code and presents them to the user. For ease of implementation, you will use a third party library to achieve this: 1. Add the following dependencies to your `app/build.gradle.kts` file: ```kotlin title="app/build.gradle.kts" implementation("com.google.accompanist:accompanist-permissions:0.36.0") implementation("com.journeyapps:zxing-android-embedded:4.3.0") ``` 2. [Sync](https://developer.android.com/build#sync-files) your project with Gradle files. 3. In your package, create a new file named `ScanOfferScreen.kt`. Scan Offer screen created 4. Add the following code to the new file: ```kotlin title="ScanOfferScreen.kt" package com.example.holdertutorial import android.Manifest import android.app.Activity import android.content.Context import android.util.Log import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import androidx.navigation.NavController import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState import com.journeyapps.barcodescanner.BarcodeCallback import com.journeyapps.barcodescanner.DecoratedBarcodeView import global.mattr.mobilecredential.holder.MobileCredentialHolder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch // Gets the permissions and shows the screen content, when the permissions are obtained @OptIn(ExperimentalPermissionsApi::class) @Composable fun ScanOfferScreen(navController: NavController) { val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA) val requestPermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {} LaunchedEffect(cameraPermissionState) { if (!cameraPermissionState.status.isGranted) { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } } if (cameraPermissionState.status.isGranted) Content(navController) } // Screen content @Composable private fun Content(navController: NavController) { val context = LocalContext.current val barcodeView = remember { DecoratedBarcodeView(context) } val coroutineScope = rememberCoroutineScope() var isQrScanned by remember { mutableStateOf(false) } val barcodeCallback = remember { BarcodeCallback { result -> // Executed when the QR code was scanned coroutineScope.launch { onQrScanned(context, result.text, navController) } barcodeView.pause() isQrScanned = true } } // Setting up the QR scanner DisposableEffect(Unit) { barcodeView.decodeContinuous(barcodeCallback) barcodeView.resume() onDispose { barcodeView.pause() } } // Showing the scanner until the QR is scanned. Showing a progress bar after that if (!isQrScanned) { AndroidView(factory = { barcodeView }, modifier = Modifier.fillMaxSize()) } else { Box(Modifier.fillMaxSize()) { CircularProgressIndicator(Modifier.align(Alignment.Center)) } } } private suspend fun onQrScanned(context: Context, offer: String, navController: NavController) { // Step 3.2: Discover credential offer } ``` 5. Back in the `MainActivity` file, add the following code under the `// Claim Credential - Step 2.5: Add Scan Offer screen call` comment to connect the created composable to the navigation graph: ```kotlin title="MainActivity.kt" ScanOfferScreen(navController) ``` 6. [Run](https://developer.android.com/studio/run#basic-build-run) the app and select the **Scan Credential Offer** button. As the user selects the **Claim Credential** button, the app asks for camera permission, and launches the device camera to enable the user to scan a QR code. 1. Replace the code in `/app/index.tsx` with the following: ```ts title="/app/index.tsx" // The Index component displays the list of credentials and enables claiming new credentials using a QR code scanner. import { useRouter } from "expo-router"; import React, { useState } from "react"; import { ActivityIndicator, Modal, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; import CredentialsList from "@/components/CredentialsList"; // Claim a Credential - Step 2.3: Import the QRCodeScanner component import { useHolder } from "@/providers/HolderProvider"; export default function Index() { const router = useRouter(); const { isHolderInitialized, error, isLoading, mobileCredentials, deleteMobileCredential, } = useHolder(); const [isScannerVisible, setIsScannerVisible] = useState(false); // Claim a Credential - Step 2.4: Define the handleScanComplete function if (isLoading) { return ( Loading... ); } if (error) { return ( Error: {error} Restart the app. ); } if (!isHolderInitialized) { return ( No holder instance ); } return ( Welcome to the mDoc Holder App {/* UI to enable the QR code scanner */} setIsScannerVisible(true)} > Claim Credential {/* Proximity Presentation - Step 1.5: Add Proximity Presentation button */} {/* Online Presentation - Step 2.7: Add Online Presentation button */} {/* Display the list of credentials and their metadata */} {/* Modal to display the QR code scanner */} {/* Claim a Credential - Step 2.5: Add the QRCodeScanner component */} ); } const styles = StyleSheet.create({ container: { flex: 1, paddingTop: 16, paddingHorizontal: 16, }, centered: { flex: 1, alignItems: "center", justifyContent: "center", }, headerText: { fontSize: 20, fontWeight: "600", marginBottom: 20, }, buttonContainer: { marginBottom: 20, gap: 10, }, button: { backgroundColor: "#007AFF", paddingVertical: 12, paddingHorizontal: 20, borderRadius: 8, alignItems: "center", justifyContent: "center", }, buttonText: { color: "white", fontSize: 16, fontWeight: "600", }, closeButton: { marginBottom: 20, backgroundColor: "#FF3B30", }, modalContainer: { flex: 1, justifyContent: "center", }, }); ``` 2. In your project's `components` directory, create a new file named `QRCodeScanner.tsx` and add the following code: ```tsx title="/components/QRCodeScanner.tsx" import React, { useRef } from "react"; import { Dimensions, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; import { Camera, useCameraDevice, useCameraPermission, useCodeScanner, } from "react-native-vision-camera"; type QRCodeScannerProps = { onScanComplete: (scannedValue: string) => void; }; const { width, height } = Dimensions.get("window"); const overlaySize = width * 0.7; export default function QRCodeScanner({ onScanComplete }: QRCodeScannerProps) { const device = useCameraDevice("back"); const { hasPermission, requestPermission } = useCameraPermission(); // Ref to track if a scan has been handled const scanHandledRef = useRef(false); const codeScanner = useCodeScanner({ codeTypes: ["qr"], onCodeScanned: ([code]) => { if (code?.value && !scanHandledRef.current) { console.log("Scanned QR Code:", code.value); scanHandledRef.current = true; onScanComplete(code.value); } }, }); // Handle cases where permissions are not granted if (!hasPermission) { return ( Request Camera Permission ); } // Handle cases where no camera device is found if (!device) { return ( No back camera device found. ); } return ( {/* Render the Camera component */} {/* QR Code Overlay */} ); } const styles = StyleSheet.create({ container: { flex: 1, }, centered: { flex: 1, justifyContent: "center", alignItems: "center", }, camera: { flex: 1, }, errorText: { fontSize: 16, color: "red", }, overlayContainer: { position: "absolute", top: 0, left: 0, width: width, height: height, justifyContent: "space-between", alignItems: "center", }, topOverlay: { width: width, height: (height - overlaySize) / 2, backgroundColor: "rgba(0, 0, 0, 0.5)", }, middleOverlay: { flexDirection: "row", }, sideOverlay: { width: (width - overlaySize) / 2, height: overlaySize, backgroundColor: "rgba(0, 0, 0, 0.5)", }, focusedArea: { width: overlaySize, height: overlaySize, borderWidth: 2, borderColor: "blue", backgroundColor: "transparent", }, bottomOverlay: { width: width, height: (height - overlaySize) / 2, backgroundColor: "rgba(0, 0, 0, 0.5)", }, button: { backgroundColor: "#007AFF", paddingVertical: 12, paddingHorizontal: 20, borderRadius: 8, alignItems: "center", }, buttonText: { color: "white", fontSize: 16, fontWeight: "600", }, }); ``` This component uses the `react-native-vision-camera` library to provide QR code scanning functionality with the following features: * **Camera Integration**: Uses the device's camera and handles permission requests. * **Visual Guidance**: Displays an overlay with a focused scanning area to help users position QR codes correctly, * **Error Handling**: Provides clear UI feedback when: * Camera permissions haven't been granted, * No compatible camera device is found, * **Scan Processing**: Captures QR code data and passes it to the parent component via the `onScanComplete` callback, 3. Next, return to the `index.tsx` file and import the `QRCodeScanner` component by adding the following code under the `// Claim a Credential - Step 2.3: Import the QRCodeScanner component` comment: ```ts title="/app/index.tsx" import QRCodeScanner from "@/components/QRCodeScanner"; ``` When the `QRCodeScanner` decodes the QR code, it will call the `onScanComplete` callback with the scanned value. This should be a URI that starts with `openid-credential-offer://`. 4. Add the following code under the `// Claim a Credential - Step 2.4: Define the handleScanComplete function` comment to handle the scanned QR code: ```ts title="/app/index.tsx" const handleScanComplete = (scannedValue: string) => { setIsScannerVisible(false); if (!scannedValue) return; if (scannedValue.startsWith("openid-credential-offer://")) { router.push({ pathname: "/claim-credential", params: { scannedValue }, }); } // Online Presentation - Step 2.2: Handle the 'mdoc-openid4vp://' scheme prefix }; ``` When called, the `handleScanComplete` function would redirect the user to the `/claim-credential` screen with the `scannedValue` parameter obtained from the QR code. This is the screen where we will display the offer details to the user and enable them to accept the offered credential. Your code editor will likely show a warning as we will only create the `/claim-credential` screen later in the tutorial. 5. Add the following code under the `{/* Claim a Credential - Step 2.5: Add the QRCodeScanner component */}` comment to combine the `QRCodeScanner` component with the `handleScanComplete` function: ```ts title="/app/index.tsx" setIsScannerVisible(false)} > setIsScannerVisible(false)} > Close Scanner ``` Now, when the `Claim Credential` button is selected the app will display the `QRCodeScanner` component in a modal. When a QR code is successfully scanned, the `onScanComplete` callback (`handleScanComplete`) is triggered, which will navigate the user to the `/claim-credential` screen with the information obtained from the QR code passed as a `scannedValue` parameter. 6. Run the app. The application will now display a **Claim Credential** button on the home screen. As the user selects this button they will be prompted for permission to access the camera, and then the QR code scanner will be displayed. You should see a result similar to the following: ### Step 3: Retrieve offer details and present them to the user [#step-3-retrieve-offer-details-and-present-them-to-the-user] Present offer details Next, you'll add the ability to display the details of the Credential offer to the user before they decide to claim any credentials. This process, known as *credential discovery*, allows your wallet application to retrieve and present the offer details, including: * What Issuer is offering the credentials? * What credentials are being offered, in what format and what claims do they include? To display this information to the user, your application should call the SDK's [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/discovercredentialoffer\(_:\)) method. We are going to implement this within the `ViewModel` class. 1. Add the following code under the `// Claim Credential - Step 3.1: Add DiscoveredCredentialOffer and discoveredCredentialOfferURL vars` comment to add new variables that will hold the credential offer details: ```swift title="ContentView" var discoveredCredentialOffer: DiscoveredCredentialOffer? var discoveredCredentialOfferURL = "" ``` 2. Replace the `print` statement under the `// Claim Credential - Step 3.2: Add discover credential offer logic` comment with the following code to create a function that calls the SDK's [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/discovercredentialoffer\(_:\)) method: ```swift title="ContentView" Task { do { discoveredCredentialOffer = try await mobileCredentialHolder.discoverCredentialOffer(offer) // save the url to use for credential retrieval discoveredCredentialOfferURL = offer // present credential offer screen, as soon as credential offer is discovered navigationPath.append(NavigationState.credentialOffer) } catch { print(error) } } ``` This function is called from our `QRScannerView` callback, so that when the user scans a QR Code that includes a credential offer, the [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/discovercredentialoffer\(_:\)) method is called and accepts the returned `credentialOffer` string as its `offer` parameter. This is a URL-encoded [Credential offer](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer) which in our example is embedded in a QR code. In other implementations you might have to retrieve this parameter from a deep-link. The [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/discovercredentialoffer\(_:\)) method makes a request to the `offer` URL to retrieve the offer details and returns it as a [`DiscoveredCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/discoveredcredentialoffer) object: ```swift title="Swift" struct DiscoveredCredentialOffer { let issuer: URL let credentials: [OfferedCredential] let transactionCode: TransactionCode? } ``` The application can now use the `issuer` and `credentials` properties and present this information for the user to review. Once an application has discovered a credential offer, the user is navigated to the `credentialOfferView` view, which you are going to implement next. Next you will use the `transactionCode` property to inspect whether or not the issuer requires a transaction code to claim the credential. This will enable the app to handle offers with and without a transaction code. 3. Create a new file named `transactionCodeInputView` and paste the following code to create a view that allows the user to input a transaction code when it is required by the issuer: ```swift title="transactionCodeInputView" import SwiftUI struct TransactionCodeInputView: View { var viewModel: ViewModel @State private var transactionCode = "" @Environment(\.dismiss) private var dismiss var body: some View { VStack(spacing: 20) { Text("Transaction Code Required") .font(.title2) .fontWeight(.bold) Text("Please enter the transaction code to proceed with credential retrieval.") .multilineTextAlignment(.center) .foregroundColor(.secondary) TextField("Enter transaction code", text: $transactionCode) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.horizontal) HStack(spacing: 20) { Button("Cancel") { dismiss() } .buttonStyle(.bordered) Button("Retrieve Credentials") { viewModel.retrieveCredential(transactionCode: transactionCode) } .buttonStyle(.borderedProminent) .disabled(transactionCode.isEmpty) } Spacer() } .padding() .navigationTitle("Transaction Code") .navigationBarBackButtonHidden(false) } } ``` The application will only show this view when the credential offer indicates a transaction code is required. The user will then be able to input a transaction code they had received separately from the issuer. 4. Return to `ContentView` file and replace the `EmptyView` under the `// Claim Credential - Step: 3.4 Display transaction code input view` comment with the following code to make use of the new view: ```swift title="Swift" TransactionCodeInputView(viewModel: viewModel) ``` 5. Replace the `EmptyView` under the `// Claim Credential - Step 3.5: Display Credential offer` comment with the following code to navigate the user to the `credentialOfferView` view when a credential offer is discovered: ```swift title="ContentView" VStack { Text("Received \(viewModel.discoveredCredentialOffer?.credentials.count ?? 0) Credential Offer(s)") .font(.headline) Text("from \(viewModel.discoveredCredentialOffer?.issuer.absoluteString ?? "unknown issuer")") .font(.subheadline) List(viewModel.discoveredCredentialOffer?.credentials ?? [], id: \.docType) { credential in Section { HStack { Text("Name:") .bold() Spacer() Text("\(credential.name ?? "")") } HStack { Text("Doctype:") .bold() Spacer() Text("\(credential.docType)") } HStack { Text("No. of claims:") .bold() Spacer() Text("\(credential.claims?.count ?? 0)") } } } Button { if viewModel.discoveredCredentialOffer?.transactionCode != nil { viewModel.navigationPath.append(NavigationState.transactionCodeInput) return } viewModel.retrieveCredential(transactionCode: nil) } label: { Text("Consent and retrieve Credential(s)") .font(.title3) } .buttonStyle(.borderedProminent) .clipShape(Capsule()) } ``` The app now handles selection of the **Consent and retrieve Credential(s)** button based on the retrieved offer details: * For Pre-authorized Code offers: * If a transaction code is required, it will navigate the user to the `TransactionCodeInputView` view. * If no transaction code is required, it will call `viewModel.retrieveCredential(transactionCode: nil)` to retrieve the credential. * For Authorization Code offers: * The SDK will automatically redirect the user to a web browser to authenticate with issuer before continuing to retrieve the credential. 6. [Run](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device) the app, select the **Claim Credential** button and scan the following QR code: QR Code You should see a result similar to the following: As the user scans the QR code, the application displays the credential offer details. You might notice that nothing happens if you select the **Consent and retrieve Credential(s)** button. This is expected - in the next step you will implement the logic that initiates the credential issuance once the user provides their consent. To display this information to the user, your application should call the [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/discover-credential-offer.html) function. 1. In your `MainActivity` file, add the following code under the `// Claim Credential - Step 3.1: Add discovered credential offer variables` comment to add new variables that will hold the credential offer details: ```kotlin title="MainActivity.kt" var scannedOffer: String? = null var discoveredCredentialOffer: DiscoveredCredentialOffer? = null ``` 2. In your `ScanOfferScreen` file, add the following code under the `// Step 3.2: Discover credential offer` comment to handle the credential offer upon scanning a QR code: ```kotlin title="ScanOfferScreen.kt" try { SharedData.scannedOffer = offer SharedData.discoveredCredentialOffer = MobileCredentialHolder.getInstance().discoverCredentialOffer(offer) } catch (e: Exception) { Toast.makeText(context, "Failed to discover offer", Toast.LENGTH_SHORT).show() } navController.navigateUp() ``` Now, once the user scans a QR Code, the [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/discover-credential-offer.html) function is called and accepts the returned `offer` string as its parameter. This is a URL-encoded [Credential offer](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer) which in our example was embedded in a QR code. In other implementations you might have to retrieve this parameter from a deep-link. The [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/discover-credential-offer.html) function makes a request to the `offer` URL to retrieve the offer information and returns it as a [`DiscoveredCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.issuance.dto/-discovered-credential-offer/index.html) object: ```text title="DiscoveredCredentialOffer structure" DiscoveredCredentialOffer { issuer: String, credentials: [OfferedCredential], transactionCode: TransactionCode? } ``` * The application can now use the `issuer` and `credentials` properties and present this information for the user to review. * The `transactionCode` property is optional and is only used in the Pre-authorized Code flow when the issuer configures it as part of the issuance flow. 3. In your `MainActivity` file, add the following code under the `// Claim Credential - Step 3.3: Display discovered credential offer` to display the offer details to the user: ```kotlin title="MainActivity.kt" SharedData.discoveredCredentialOffer?.let { discoveredOffer -> Text("Received Credential Offer from ${discoveredOffer.issuer}") LazyColumn(Modifier.fillMaxWidth()) { items(discoveredOffer.credentials, key = { it.docType }) { credential -> Card(Modifier.fillMaxWidth()) { Column(Modifier.padding(4.dp)) { Text("Name: ${credential.name ?: ""}") Text("DocType: ${credential.docType}") } } } } // Claim Credential - Step 4.3: Add transaction code input // Claim Credential - Step 4.5: Add consent button } ``` 4. [Run](https://developer.android.com/studio/run#basic-build-run) the app, select the **Scan Credential Offer** button and scan the following QR code: QR Code You should see a result similar to the following: As the user scans the QR code, the application displays the Credential offer details. To display this information to the user, your holder application should call the SDK's [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/discoverCredentialOffer.html) function, passing the OID4VCI credential offer endpoint's URI as its `uri` parameter. This URI is retrieved by the application from the scanned QR code. 1. Create a new file named `/app/claim-credential.tsx` and add the following scaffolding code: ```ts title="/app/claim-credential.tsx" import { useGlobalSearchParams, useRouter } from "expo-router"; import React, { useCallback, useEffect, useState } from "react"; import { ActivityIndicator, Alert, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from "react-native"; import { useHolder } from "@/providers/HolderProvider"; import { discoverCredentialOffer, retrieveCredentials, } from "@mattrglobal/mobile-credential-holder-react-native"; import type { DiscoveredCredentialOffer, OfferedCredential, } from "@mattrglobal/mobile-credential-holder-react-native/lib/types/index"; // Claim a Credential - Step 4.1: define the CLIENT_ID constant export default function ClaimCredential() { const router = useRouter(); const { scannedValue } = useGlobalSearchParams<{ scannedValue: string }>(); const { isHolderInitialized, getMobileCredentials } = useHolder(); const [credentialOffer, setCredentialOffer] = useState(); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [transactionCode, setTransactionCode] = useState(""); useEffect(() => { const discoverOffer = async () => { if (!isHolderInitialized || !scannedValue) { setError("Missing holder instance or scanned value."); setIsLoading(false); return; } // Claim Credential - Step 3.2: Discover Credential Offer }; discoverOffer(); }, [isHolderInitialized, scannedValue]); const handleConsent = useCallback(async () => { if (!credentialOffer || !isHolderInitialized) { Alert.alert("Error", "Missing offer information or holder instance."); router.back(); return; } // Claim a Credential - Step 4.2: Retrieve Credentials }, [credentialOffer, isHolderInitialized, getMobileCredentials, router, scannedValue, transactionCode]); if (isLoading) { return ( Discovering offer... ); } if (error || !credentialOffer) { return ( Error: {error || "No discovery offer found."} router.back()} > Go Back ); } // Claim a Credential - Step 3.3: Display the offer details to the user } const styles = StyleSheet.create({ container: { flex: 1, padding: 16, backgroundColor: "#fff", }, centered: { flex: 1, alignItems: "center", justifyContent: "center", }, title: { fontSize: 22, fontWeight: "600", marginBottom: 10, color: "#000", }, text: { fontSize: 16, color: "#333", marginBottom: 10, }, card: { marginBottom: 16, padding: 10, backgroundColor: "#F5F5F5", borderRadius: 8, borderWidth: 1, borderColor: "#E0E0E0", }, cardTitle: { fontWeight: "700", fontSize: 18, marginBottom: 5, color: "#000", }, button: { backgroundColor: "#007AFF", paddingVertical: 12, paddingHorizontal: 20, borderRadius: 8, alignItems: "center", }, buttonText: { color: "white", fontSize: 16, fontWeight: "600", }, buttonContainer: { marginBottom: 20, gap: 10, }, errorText: { color: "#FF3B30", textAlign: "center", }, transactionCodeContainer: { marginBottom: 16, padding: 16, backgroundColor: "#F8F9FA", borderRadius: 8, borderWidth: 1, borderColor: "#E0E0E0", }, description: { fontSize: 14, color: "#666", marginBottom: 8, fontStyle: "italic", }, textInput: { borderWidth: 1, borderColor: "#DDD", borderRadius: 8, padding: 12, fontSize: 16, backgroundColor: "white", marginTop: 8, }, }); ``` 2. Add the following code under the `// Claim a Credential - Step 3.2: Discover Credential Offer` comment to call the SDK's [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/discoverCredentialOffer.html) function and discover the credential offer: ```ts title="/app/claim-credential.tsx" const discoveryResult = await discoverCredentialOffer(scannedValue); if (discoveryResult.isErr()) { setError(`Error discovering credential offer: ${discoveryResult.error.message || discoveryResult.error.type}`); } else { console.log("Discovered Credential Offer:", discoveryResult.value); setCredentialOffer(discoveryResult.value); } setIsLoading(false); ``` Now, once the user scans a QR Code that includes a credential offer (e.g. it is prefixed with `openid-credential-offer://`), the SDK's [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/discoverCredentialOffer.html) function is called and accepts the returned `scannedValue` string (Offer URI) as its `uri` parameter. This is a URL-encoded [Credential offer](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer) which in our example was embedded in a QR code. In other implementations you might have to retrieve this parameter from a deep-link. The [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/discoverCredentialOffer.html) function makes a request to the `uri` URL to retrieve the offer information and returns it as a [`DiscoveredCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/DiscoveredCredentialOffer.html) object: ```ts title="DiscoveredCredentialOffer structure" DiscoveredCredentialOffer: { issuer: string; credentials: OfferedCredential[]; transactionCode?: { description?: string; length?: number; }; } ``` Your application can now use the `issuer` (who is offering the credentials) and `credentials` (what credentials are offered and what claims they contain) properties and present this information for the user to review. 3. Add the following code under the `// Claim a Credential - Step 3.3: Display the offer details to the user` comment to display the offer details to the user: ```ts title="/app/claim-credential.tsx" return ( Credential Offer Received {credentialOffer.credentials.length} credential {credentialOffer.credentials.length > 1 ? "s" : ""} from {credentialOffer.issuer} {/* Claim a Credential - Step 4.3: Add transaction code input if required */} {credentialOffer?.transactionCode && ( Transaction Code Required: {credentialOffer.transactionCode.description && ( {credentialOffer.transactionCode.description} )} )} {credentialOffer.credentials.map((cred: OfferedCredential, index: number) => ( {cred.name} Document Type: {cred.docType} Number of Claims: {cred.claims?.length} ))} {/* Claim a Credential - Step 4.4: Add the Consent and Retrieve button */} ); ``` 4. Run the app, select the **Claim Credential** button and scan the following QR code: QR Code You should see a result similar to the following: As the user scans the QR code, the holder application displays the Credential offer details. ### Step 4: Obtain user consent and initiate credential issuance [#step-4-obtain-user-consent-and-initiate-credential-issuance] Present offer details The next (and final!) step is to build the capability for the user to accept the credential offer. This should then trigger issuing the credential and storing it in the application storage. Once the user provides their consent by selecting the **Consent and retrieve Credential(s)** button, your application must call the SDK's [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/preview/documentation/mobilecredentialholdersdk/mobilecredentialholder/retrievecredentials\(credentialoffer:clientid:transactioncode:\)) function to trigger the credential issuance and store the issued credential in the application storage. * For **Pre-authorized Code offers**, this will happen within the application and after the user provided a transaction code (when required by the issuer). * For **Authorization Code offers**, this will happen after the user had completed authentication with the issuer and was redirected back to the application. 1. Add the following code under the `// Claim Credential - Step 4.1: Add retrievedCredentials var` comment to add a new variable that will hold the result returned by the SDK's [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/preview/documentation/mobilecredentialholdersdk/mobilecredentialholder/retrievecredentials\(credentialoffer:clientid:transactioncode:\)) method: ```swift title="ContentView" var retrievedCredentials: [MobileCredential] = [] ``` 2. Replace the `print` statement under the `// Claim Credential - Step 4.2: Call retrieveCredential method` comment with the following code to create a new function that will call the SDK's [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/preview/documentation/mobilecredentialholdersdk/mobilecredentialholder/retrievecredentials\(credentialoffer:clientid:transactioncode:\)) method: ```swift title="ContentView" Task { do { let retrievedCredentialResults = try await mobileCredentialHolder.retrieveCredentials( credentialOffer: discoveredCredentialOfferURL, clientId: Constants.clientId, transactionCode: transactionCode ) Task { var credentials: [MobileCredential] = [] for result in retrievedCredentialResults { switch result { case .success(_, let credentialId): if let credential = try? await mobileCredentialHolder.getCredential(credentialId: credentialId) { credentials.append(credential) } case .failure(let docType, let error): print("Failed to retrieve \(docType): \(error)") } } self.retrievedCredentials = credentials // Clear navigation stack and display retrievedCredentials view navigationPath = NavigationPath() navigationPath.append(NavigationState.retrievedCredentials) } } catch { print(error.localizedDescription) } } ``` Let’s review the parameters passed to the `retrieveCredentials` function: * `credentialOffer`: This is the same credential offer string from the QR Code that we used to call [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/discovercredentialoffer\(_:\)) with. * `clientId`: This was configured when [setting up your development environment](#configure-required-resources). It is used by the issuer to identify the wallet application that is making a request to claim credentials. * `transactionCode`: This is only required for Pre-authorized Code credential offers. If your application is only using Authorization Code flows offers, you should set it to `nil`. The [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/preview/documentation/mobilecredentialholdersdk/mobilecredentialholder/retrievecredentials\(credentialoffer:clientid:transactioncode:\)) function returns an array of [`RetrieveCredentialResult`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/retrievecredentialresult) objects. Each object contains metadata about a credential that was retrieved: ```swift title="RetrieveCredentialResult" enum RetrieveCredentialResult { case success(docType: String, credentialId: String) case failure(docType: String, error: RetrieveCredentialError) } [ { "docType":"org.iso.18013.5.1.mDL", "credentialId":"F52084CF-8270-4577-8EDD-23149639D985" } ] ``` `RetrieveCredentialResult` is an enum with two cases, each carrying guaranteed values: * `.success` : Carries the `docType` and the `credentialId` (the internally unique identifier) of a successfully retrieved credential. * `.failure` : Carries the `docType` and the `error` describing why retrieval failed. After the result is received, your application can retrieve specific credentials by calling the SDK's [`getCredential`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/getcredentials\(\)) method with the `credentialId` of any retrieved credential. The SDK's [getCredential](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/getcredentials\(\)) method returns a [`MobileCredential`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredential) object which can be used to display the retrieved credential, its claims and verification status to the user. Since this object can be used across multiple views, it will make sense to create one view that will represent it. We will use this view in both the [Proximity](/docs/holding/proximity-presentation-tutorial) and [Online](/docs/holding/remote-presentation-tutorial) presentation tutorials. 3. Create a new file named `DocumentView` and add the following content: ```swift title="DocumentView" import MobileCredentialHolderSDK import SwiftUI struct DocumentView: View { var viewModel: DocumentViewModel var body: some View { VStack(alignment: .leading, spacing: 10) { Text(viewModel.docType) .font(.title) .fontWeight(.bold) .padding(.bottom, 5) ForEach(viewModel.namespacesAndClaims.keys.sorted(), id: \.self) { key in VStack(alignment: .leading, spacing: 5) { Text(key) .font(.headline) .padding(.vertical, 5) .padding(.horizontal, 10) .background(Color.gray.opacity(0.2)) .cornerRadius(5) ForEach(viewModel.namespacesAndClaims[key]!.keys.sorted(), id: \.self) { claim in HStack { Text(claim) .fontWeight(.semibold) Spacer() Text(viewModel.namespacesAndClaims[key]![claim]! ?? "") .fontWeight(.regular) } .padding(.vertical, 5) .padding(.horizontal, 10) .background(Color.white) .cornerRadius(5) .shadow(radius: 1) } } .padding(.vertical, 5) } } .padding() .background(RoundedRectangle(cornerRadius: 10).fill(Color.white).shadow(radius: 5)) .padding(.horizontal) } } // MARK: DocumentViewModel class DocumentViewModel { var docType: String var namespacesAndClaims: [String: [String: String?]] init(from credential: MobileCredential) { self.docType = credential.docType self.namespacesAndClaims = credential.claims.reduce(into: [String: [String: String]]()) { result, outerElement in let (outerKey, innerDict) = outerElement result[outerKey] = innerDict.mapValues { $0.textRepresentation } } } init(from credentialMetadata: MobileCredentialMetadata) { self.docType = credentialMetadata.docType var result: [String: [String: String?]] = [:] credentialMetadata.claims.forEach { namespace, claimIDs in var transformedClaims: [String: String?] = [:] claimIDs.forEach { claimID in transformedClaims[claimID] = Optional.none } result[namespace] = transformedClaims } self.namespacesAndClaims = result } init(from request: MobileCredentialRequest) { self.docType = request.docType self.namespacesAndClaims = request.namespaces.reduce(into: [String: [String: String?]]()) { result, outerElement in let (outerKey, innerDict) = outerElement result[outerKey] = innerDict.mapValues { _ in nil } } } } // MARK: Helper extension MobileCredentialElementValue { var textRepresentation: String { switch self { case .bool(let bool): return "\(bool)" case .string(let string): return string case .int(let int): return "\(int)" case .unsigned(let uInt): return "\(uInt)" case .float(let float): return "\(float)" case .double(let double): return "\(double)" case let .date(date): let dateFormatter = DateFormatter() dateFormatter.dateStyle = .short dateFormatter.timeStyle = .none return dateFormatter.string(from: date) case let .dateTime(date): let dateFormatter = DateFormatter() dateFormatter.dateStyle = .short dateFormatter.timeStyle = .short return dateFormatter.string(from: date) case .data(let data): return "Data \(data.count) bytes" case .map(let dictionary): let result = dictionary.mapValues { value in value.textRepresentation } return "\(result)" case .array(let array): return array.reduce("") { partialResult, element in partialResult + element.textRepresentation } .appending("") @unknown default: return "Unknown type" } } } ``` The file comprises the following components: * `DocumentViewModel`: This class stores the credentials' docType and claim values. * `DocumentView`: This view takes `DocumentViewModel` as a parameter and displays its content in a human-readable format. * `MobileCredentialElementValue` : This helper extension allows retrieving a `MobileCredentialElementValue` from a `MobileCredential`'s `claims` and present it in a human-readable format. 4. Return to `ContentView` and replace `EmptyView` under the `// Claim Credential - Step 4.4: Display retrieved credentials` comment with the following code to use the `DocumentView` structure to display retrieved credentials to the user: ```swift title="ContentView" ScrollView { VStack { Text("Retrieved Credentials") .font(.title) ForEach(viewModel.retrievedCredentials, id: \.id) { credential in DocumentView(viewModel: DocumentViewModel(from: credential)) } } } ``` Once the app calls the `retrieveCredentials` function, the SDK processes the response based on the type of credential offer retrieved: * In the **Authorization Code flow**: * The user is redirected to [authenticate](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-10.html#name-authorized-code-flow) with the configured Authentication provider defined in the `authorizeEndpoint` element of the `DiscoveredCredentialOffer` object. * Upon successful authentication, the user can proceed to complete the [OID4VCI workflow](/docs/issuance/oid4vci-overview) configured by the issuer. This workflow can include different steps based on the issuer’s configuration, but eventually the user is redirected to the configured `redirectUri` which should be handled by your application. * In the **Pre-authorized Code flow** the user is not redirected out of the application, but rather provides a transaction code (when required by the issuer) and the immediately proceeds to claiming the credential. If no transaction code is required, the user can claim the credential immediately after selecting the **Consent and retrieve Credential(s)** button. The issuer then sends the issued mDocs to your application, and the SDK processes and validates them against the [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) standard. Credentials who meet validation rules are stored in the application internal data storage. In our example this is achieved by selecting a **Consent and retrieve Credential(s)** button. Once the user provides their consent by selecting this button, your application must call the [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/retrieve-credentials.html) function to trigger the credential issuance. * For **Pre-authorized Code offers**, this will happen within the application and after the user provided a transaction code (when required by the issuer). * For **Authorization Code offers**, this will happen after the user had completed authentication with the issuer and was redirected back to the application. 1. In your `MainActivity` file, add the following code under the `// Step 4.1: Add credential issuance configuration` comment to configure the redirect URI that will be used to redirect the user back to your application after completing the authentication: ```kotlin title="MainActivity.kt" credentialIssuanceConfiguration = CredentialIssuanceConfiguration( redirectUri = "io.mattrlabs.sample.mobilecredentialtutorialholderapp:" + "//credentials/callback", autoTrustMobileCredentialIaca = true ) ``` * `redirectUri` is constructed of the same `scheme` and `host` values you have [set](#add-required-dependencies) in the `AndroidManifest.xml` file. * This configuration is only required for the Authorization Code flow offers. If your application is only using Pre-authorized Code flow offers you can remove it. 2. Add the following code under the `// Claim Credential - Step 4.2: Add retrieved credentials variable` comment to add a new variable that will hold the result returned by the [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/retrieve-credentials.html) function: ```kotlin title="MainActivity.kt" var retrievedCredentials: List = emptyList() ``` 3. Add the following code under the `// Claim Credential - Step 4.3: Add transaction code input` comment to add a new text input field to allow the user to provide a transaction code when one is provided with a Pre-authorized Code flow offer: ```kotlin title="MainActivity.kt" if (discoveredOffer.transactionCode != null) { OutlinedTextField( value = transactionCode, onValueChange = { transactionCode = it }, label = { Text("Transaction Code") }, modifier = Modifier.fillMaxWidth() ) } ``` This is only required for Pre-authorized Code flow offers. If your application should only handle Authorization Code flow offers you can remove it. 4. Add the following code under the `// Claim Credential - Step 4.4: Create function to retrieve credentials` comment to create a new function that will call the [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/retrieve-credentials.html) method when the user gives the consent for the credentials retrieval: ```kotlin title="MainActivity.kt" private fun onRetrieveCredentials( coroutineScope: CoroutineScope, activity: Activity, navController: NavController, transactionCode: String ) { coroutineScope.launch { try { val mdocHolder = MobileCredentialHolder.getInstance() val retrieveCredentialResults = mdocHolder.retrieveCredentials( activity, SharedData.scannedOffer!!, // [!code highlight] clientId = "android-mobile-credential-tutorial-holder-app", // [!code highlight] transactionCode = transactionCode // [!code highlight] ) // Claim Credential - Step 4.6: Display retrieved credentials } catch (e: Exception) { Toast.makeText(activity, "Failed to retrieve credentials", Toast.LENGTH_SHORT).show() } } } ``` * `SharedData.scannedOffer` is the same offer String that was used for the [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/discover-credential-offer.html) function call. * `clientId` is used by the issuer to recognize the application. This is only used internally in the interaction between the application and the issuer and can be any string as long as it is registered with the issuer as a trusted application. * `transactionCode` is only required for Pre-authorized Code credential offers. If your application is only using Authorization Code flows offers you can remove it. 5. Add the following code under the `// Claim Credential - Step 4.5: Add consent button` comment to add a button for calling the new function: ```kotlin title="MainActivity.kt" Spacer(Modifier.weight(1f)) Button( onClick = { onRetrieveCredentials(coroutineScope, activity, navController, transactionCode) }, Modifier.fillMaxWidth() ) { Text("Consent and retrieve Credential(s)") } ``` The [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/retrieve-credentials.html) function then returns a [`RetrieveCredentialResult`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.issuance.dto/-retrieve-credential-result/index.html) list, which references all retrieved credentials: ```kotlin title="RetrieveCredentialResult structure" [RetrieveCredentialResult] sealed interface RetrieveCredentialResult { val docType: String data class Success( override val docType: String, val credentialId: String ) : RetrieveCredentialResult data class Failure( override val docType: String, val error: RetrieveCredentialError ) : RetrieveCredentialResult } [ { "docType":"org.iso.18013.5.1.mDL", // [!code highlight] "credentialId":"F52084CF-8270-4577-8EDD-23149639D985" // [!code highlight] } ] ``` `RetrieveCredentialResult` is a sealed interface with two variants, each carrying guaranteed properties: * `Success` : Carries the `docType` and the `credentialId` (UUID) of a successfully retrieved credential. * `Failure` : Carries the `docType` and the `error` describing why retrieval failed. Your application can now retrieve specific credentials by calling the [`getCredential`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/get-credential.html) function with the `credentialId` of any of the retrieved credentials. The [getCredential](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/get-credential.html) function returns a [`MobileCredential`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.dto/-mobile-credential/index.html) object which represents the issued mDoc, and your application can now introduce UI elements to enable the user to view the credential. 6. Add the following code under the `// Claim Credential - Step 4.6: Display retrieved credentials` comment to retrieve the credentials by their IDs from the local storage, save them to the `SharedData.retrievedCredentials` variable and navigate to the `retrievedCredential` screen to display the retrieved credential: ```kotlin title="MainActivity.kt" SharedData.retrievedCredentials = retrieveCredentialResults.mapNotNull { result -> when (result) { is RetrieveCredentialResult.Success -> try { mdocHolder.getCredential(result.credentialId, fetchUpdatedStatusList = false) } catch (e: Exception) { val msg = "Failed to get credential from storage" Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show() null } is RetrieveCredentialResult.Failure -> { val msg = "Failed to retrieve ${result.docType}: ${result.error}" Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show() null } } } navController.navigate("retrievedCredential") SharedData.discoveredCredentialOffer = null ``` 7. In your package, create a new file named `RetrievedCredentialsScreen.kt`. 8. Add the following code to the new file to display the `docType` and `claims` of retrieved credentials to the user: ```kotlin title="RetrievedCredentialsScreen.kt" package com.example.holdertutorial import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import global.mattr.mobilecredential.holder.deviceretrieval.deviceresponse.NameSpace import global.mattr.mobilecredential.holder.dto.MobileCredentialElement @Composable fun RetrievedCredentialsScreen() { if (SharedData.retrievedCredentials.isEmpty()) { Text("No credentials received") } else { Column { Text( "Retrieved Credentials", modifier = Modifier.fillMaxWidth(), style = MaterialTheme.typography.titleLarge ) LazyColumn(Modifier.fillMaxWidth()) { items(SharedData.retrievedCredentials, key = { it.id }) { credential -> Document( credential.docType, credential.claims.mapValues { (_, claims) -> claims.map { (name, value) -> "$name: ${value.toUiString()}" }.toSet() } ) } } } } } @Composable fun Document(docType: String, namespacesAndClaims: Map>) { Column(Modifier.fillMaxWidth().padding(6.dp)) { Text(docType, Modifier.padding(6.dp), style = MaterialTheme.typography.titleMedium) namespacesAndClaims.forEach { (namespace, claims) -> Text(namespace, Modifier.padding(6.dp), style = MaterialTheme.typography.titleSmall) Column( Modifier .padding(6.dp) .fillMaxWidth() .background(MaterialTheme.colorScheme.background, RoundedCornerShape(6.dp)) .padding(6.dp) ) { claims.forEach { claim -> Text(claim) } } } } } fun MobileCredentialElement.toUiString() = when (this) { is MobileCredentialElement.ArrayElement, is MobileCredentialElement.DataElement, is MobileCredentialElement.MapElement -> this::class.simpleName ?: "Unknown element" else -> value.toString() } ``` 9. Back in your `MainActivity` file, add the following code under the `// Claim Credential - Step 4.9: Add "Retrieved Credential" screen call` comment to connect the created composable to the navigation graph: ```kotlin title="MainActivity.kt" RetrievedCredentialsScreen() ``` Once the app calls the `retrieveCredentials` function, the SDK processes the response based on the type of credential offer retrieved: * In the **Authorization Code flow**: * The user is redirected to [authenticate](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-10.html#name-authorized-code-flow) with the configured Authentication provider defined in the `authorizeEndpoint` element of the `DiscoveredCredentialOffer` object. * Upon successful authentication, the user can proceed to complete the [OID4VCI workflow](/docs/issuance/oid4vci-overview) configured by the issuer. This workflow can include different steps based on the issuer’s configuration, but eventually the user is redirected to the configured `redirectUri` which should be handled by your application. * In the **Pre-authorized Code flow** the user is not redirected out of the application, but rather provides a transaction code (when required by the issuer) and the immediately proceeds to claiming the credential. If no transaction code is required, the user can claim the credential immediately after selecting the **Consent and retrieve Credential(s)** button. The issuer then sends the issued mDocs to your application, and the SDK processes and validates them against the [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) standard. Credentials who meet validation rules are stored in the application internal data storage. We will implement this by adding a **Consent and Retrieve** button. Once the user provides their consent by selecting this button, your application must call the [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/retrieveCredentials.html) function to trigger the credential issuance. * For **Pre-authorized Code offers**, this will happen within the application and after the user provided a transaction code (when required by the issuer). * For **Authorization Code offers**, this will happen after the user had completed authentication with the issuer and was redirected back to the application. 1. Add the following constant variables to represent the Authentication provider we will use for this tutorial under the `// Claim a Credential - Step 4.1: define the CLIENT_ID constant` comment: ```ts title="/app/claim-credential.tsx" const CLIENT_ID = "react-native-mobile-credential-holder-tutorial-app"; ``` * `CLIENT_ID` : This is the identifier that is used by the issuer to recognize the holder application. This is only used internally in the interaction between the holder application and the issuer and can be any string as long as it is registered with the issuer as a trusted holder application. The `CLIENT_ID` and `redirectUri` (configured during [SDK initialization](#step-1-initialize-the-sdk)) are both required parameters for the OID4VCI Authorization Code workflow. For this tutorial you will be claiming a credential from a MATTR Labs issuer which is pre-configured with the correct parameters. We will help you configure your unique values as you move your implementation into production. 2. Add the following code under the `// Claim a Credential - 4.2: Retrieve Credentials` comment to call the SDK's `retrieveCredentials` function to retrieve the offered credentials: ```ts title="/app/claim-credential.tsx" const retrieved = await retrieveCredentials({ credentialOffer: scannedValue, // Now takes the original URL string clientId: CLIENT_ID, transactionCode: credentialOffer?.transactionCode ? transactionCode || undefined : undefined, }); if (retrieved.isErr()) { setError( retrieved.error.message || "An unexpected error occurred during offer discovery.", ); Alert.alert( "Error", retrieved.error.message || "Failed to retrieve credentials.", ); } else { Alert.alert( "Success", `Retrieved ${retrieved.value.length} credential(s) successfully!`, ); } // Refresh the list of mobile credentials in the holder application await getMobileCredentials(); router.replace("/"); ``` Let's review where all the parameters come from: * `credentialOffer`: Now takes the original URL string (`scannedValue`) directly instead of the `CredentialOfferResponse` object. The SDK handles the offer discovery internally. * `CLIENT_ID`: Defined in the `claim-credential.tsx` file. It is used by the issuer to identify the holder application that is making a request to claim credentials. * `transactionCode`: Optional parameter used for pre-authorized flows. When the credential offer requires a transaction code, this should be provided by the user. Set to `undefined` for regular authorization code flows. 3. Add the following code under the `// Claim a Credential - Step 4.4: Add the Consent and Retrieve button` comment to display a **Consent and Retrieve** button which call the applications' `handleConsent` function: ```ts title="/app/claim-credential.tsx" Consent and Retrieve router.replace("/")} > Cancel ``` Authentication Once the `handleConsent()` function is called, it will execute the `retrieveCredentials` function. The user will be redirected to [authenticate](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-10.html#name-authorized-code-flow) with the configured Authentication provider as determined by the issuer's OID4VCI workflow configuration. This tutorial uses a demo MATTR Labs Credential offer to issue the credential. This offer uses a workflow that doesn't actually authenticate the user before issuing a credential, but redirects them to select the credential they wish to issue. In production implementations this must be replaced by a proper authentication mechanism to comply with the ISO/IEC 18013-5:2021 standard and the OID4VCI specification. Upon successful authentication, the user will proceed to complete the [OID4VCI workflow](/docs/issuance/oid4vci-overview) configured by the issuer. This workflow can include different steps based on the issuer’s configuration, but eventually the user is redirected to the configured `REDIRECT_URI` which should be handled by your application. In the example Credential offer used in this tutorial, the issuance workflow immediately proceeds to claiming the credential. Check out our other issuance guides and tutorials for creating more rich and flexible user experiences. As the user is redirected to `REDIRECT_URI`, the issuer sends the issued mDocs to your application. The SDK then processes the received credentials and validates them against the [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) standard. Credentials who meet validation rules are stored in the application. Let's test the end-to-end flow of claiming a credential using the application you had just built. ### Step 5: Test the application [#step-5-test-the-application] #### Authorization code flow [#authorization-code-flow] 1. Run the application. 2. Select the **Claim Credential** button. 3. Scan the following QR code: QR Code 4. Select the **Consent and retrieve Credential(s)** button. You should see a result similar to the following: As the user scans the QR code, the wallet retrieves and displays the offer details. The user then provides consent to retrieving the credentials, and the wallet responds by initiating the issuance workflow and displaying the retrieved credentials to the user. This tutorial uses a demo MATTR Labs Credential offer to issue the credential. This offer uses a workflow that doesn't actually authenticate the user before issuing a credential, but redirects them to select the credential they wish to issue. In production implementations this must be replaced by a proper [Authentication provider](/docs/issuance/authorization-code/authentication-provider/guide) to comply with the ISO/IEC 18013-5:2021 standard and the OID4VCI specification. Credential claimed #### Pre-authorized Code flow [#pre-authorized-code-flow] Let's test the end-to-end flow of claiming a credential using the Pre-authorized Code flow: 1. Open the MATTR Labs [Pre-authorized Offer tool](https://tools.mattrlabs.com/issue-credential). 2. Turn on the *Transaction Code* option for the tool to generate a transaction code. 3. Select the **Generate Credential Offer** button.\ A QR code will be generated and rendered on the screen, along with a transaction code. 4. Run your application. 5. Select the **Claim Credential** button. 6. Scan the QR code. 7. Enter the transaction code retrieved from the MATTR Labs tool. 8. Select the **Consent and retrieve Credential(s)** button. In production implementations, the transaction code is generated by the issuer and shared with the intended holder by a separate secure channel. In this tutorial, the transaction code is generated by the MATTR Labs tool and displayed on the screen for demonstration purposes and to simplify testing. Congratulations! Your application can now interact with an OID4VCI Credential offer to claim mDocs! ## Summary [#summary] You have just used the [mDocs Holder SDKs](/docs/holding/sdk-overview) to build an application that can claim an [mDoc](/docs/concepts/mdocs) issued via [OID4VCI](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html), supporting both the [Authorization Code](/docs/issuance/authorization-code/overview) and [Pre-authorized Code](/docs/issuance/pre-authorized-code/overview) flows: OID4VCI Tutorial Workflow This was achieved by building the following capabilities into the application: 1. Initialize the SDK so the application can use its functions and classes. 2. Interact with a Credential offer formatted as a QR code. 3. Retrieve the offer details and present them to the user. 4. Obtain user consent and initiate the credential issuance workflow. ## What's next? [#whats-next] * You can build additional capabilities into your new application: * Present a claimed mDoc for verification via an [online presentation workflow](/docs/holding/remote-presentation-tutorial) into your new application. * Present a claimed mDoc for verification via a [proximity presentation workflow](/docs/holding/proximity-presentation-tutorial). * You can check out the SDKs reference documentation for more details on the available functions and classes: * [iOS](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk) * [Android](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/index.html) * [React Native](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html) # Frequently asked questions URL: /docs/holding/faq Answers to common questions about credential holding with MATTR. For the concepts behind these answers, see the [holding overview](/docs/holding). ## Frequently asked questions [#frequently-asked-questions] ### Can I add credential holding to my existing app without rebuilding it? [#can-i-add-credential-holding-to-my-existing-app-without-rebuilding-it] Yes. The MATTR Pi Holder SDK is designed to be added to existing mobile applications. You integrate it as a native dependency (CocoaPods/SPM for iOS, Maven/Gradle for Android, npm for React Native) and call SDK methods from your existing codebase. ### What's the difference between MATTR Pi Holder SDK and MATTR GO Hold? [#whats-the-difference-between-mattr-pi-holder-sdk-and-mattr-go-hold] The [MATTR Pi Holder SDK](/docs/holding/sdk-overview) is a library you embed in your own app, so you control the full user experience. [MATTR GO Hold](/docs/holding/go-hold/getting-started) is a standalone wallet application ready for end users. Choose the SDK for custom integration. Choose GO Hold for rapid deployment or pilots. ### Does the SDK work offline? [#does-the-sdk-work-offline] Yes, for proximity presentation. The SDK uses BLE for in-person credential exchange and can verify credentials against locally cached trusted issuer certificates and status lists. Credential claiming and remote presentation require network connectivity. ### Which credential formats are supported? [#which-credential-formats-are-supported] The MATTR Pi Holder SDK supports [mDocs](/docs/concepts/mdocs) (aligned with ISO/IEC 18013-5) for both claiming and presentation. This includes mobile driver's licenses (mDLs) and other mDoc-based credentials. ### How are credentials stored securely? [#how-are-credentials-stored-securely] The SDK uses platform-native secure storage: iOS Keychain and Android Keystore. Credential keys are hardware-backed where available and bound to the device. Credentials cannot be extracted or cloned to another device. ### Can my app claim credentials from any issuer? [#can-my-app-claim-credentials-from-any-issuer] Your app can claim credentials from any issuer using the [OID4VCI standard](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html). For verification of issuer trust, configure your trusted issuer certificate list to include the IACA certificates of issuers you accept. ### How do I handle credential revocation in my app? [#how-do-i-handle-credential-revocation-in-my-app] The SDK automatically checks status lists based on the issuer's `ttl` and `exp` configuration. Your app should handle the case where a previously valid credential's status changes. Surface this to the user and, if appropriate, prompt them to obtain a replacement. ### Can users present credentials to verifiers not using MATTR? [#can-users-present-credentials-to-verifiers-not-using-mattr] Yes. Because the SDK implements ISO/IEC 18013-5 (proximity) and ISO/IEC 18013-7 + OID4VP (remote), your app can present credentials to any standards-compliant verifier regardless of their platform provider. ### What happens if the user switches devices? [#what-happens-if-the-user-switches-devices] Credentials are bound to the device's secure storage and cannot be transferred. Users will need to re-claim their credentials on a new device through the original issuer's offer flow. ### How long does SDK integration typically take? [#how-long-does-sdk-integration-typically-take] Basic credential claiming can be integrated in days. A full implementation covering claiming, proximity presentation, and remote presentation with polished UX typically takes 2–4 weeks for an experienced mobile team. The [quickstart guide](/docs/holding/sdk-quickstart) gets you to a first working integration quickly. # Credential Holding URL: /docs/holding Adding digital credential holding to your mobile app means your users can receive, store, and present verifiable credentials (such as mobile driver's licenses, employee badges, or proof-of-age tokens) without leaving your application. This overview helps product and engineering teams understand the integration approach, key decisions, and available tooling. Start here, then follow the pages in order or jump to the topic you need. ## Underlying platforms [#underlying-platforms] MATTR Credential Holding capabilities MATTR offers two holding solutions: the [MATTR Pi Holder SDKs](/docs/holding/sdk-overview) for embedding credential capabilities into your own mobile app, and [MATTR GO Hold](/docs/holding/go-hold/getting-started), a ready-made wallet application you can deploy as-is or with your own branding. ## Explore the overview [#explore-the-overview] Work through these pages to plan your holding integration from simple to more complex concepts: 1. [SDK or ready-made wallet?](/docs/holding/sdk-or-wallet): the decision and a feature comparison. 2. [Platform requirements](/docs/holding/platform-requirements): minimum versions and frameworks. 3. [Core capabilities to integrate](/docs/holding/core-capabilities): claiming, proximity presentation, and remote presentation. 4. [Integration architecture](/docs/holding/integration-architecture): components and data flow. 5. [Trust and security considerations](/docs/holding/trust-and-security): trusted verifier and issuer management, holder authentication, and status lists. 6. [Operational tooling](/docs/holding/operational-tooling): activity log and SDK logging. 7. [Frequently asked questions](/docs/holding/faq): answers to common holding questions. # Integration architecture URL: /docs/holding/integration-architecture The typical integration involves your existing mobile app, the MATTR Pi Holder SDK, and MATTR VII as the backend platform. This page describes the components and how data flows between them. ## Integration architecture [#integration-architecture] The typical integration involves your existing mobile app, the MATTR Pi Holder SDK, and MATTR VII as the backend platform. ### Components [#components] | Component | Role | | --------------------- | -------------------------------------------------------------------------- | | Your mobile app | User interface, business logic, navigation | | MATTR Pi Holder SDK | Credential operations (claim, store, present) | | MATTR VII | Issuer/verifier backend (offers, sessions, trust) | | Device secure storage | Credential and key storage (managed by SDK via platform keychain/keystore) | ### Data flow [#data-flow] 1. **Claiming:** Your app receives an offer URI → passes it to the SDK → SDK communicates with MATTR VII → credential is stored locally. 2. **Proximity presentation:** Verifier scans your app's QR → BLE session established → SDK handles exchange → user consents → data shared. 3. **Remote presentation:** Verifier sends request → your app receives it → SDK processes and validates → user consents → response sent. ## Next steps [#next-steps] Next, review the [trust and security considerations](/docs/holding/trust-and-security) for your integration. # Operational tooling URL: /docs/holding/operational-tooling The Holder SDK provides tooling to help you build user-facing history views and to debug your integration during development. ## Operational tooling [#operational-tooling] ### Activity log [#activity-log] The SDK provides an [activity log](/docs/holding/sdk-operations/activity-log) that records credential lifecycle events (claimed, presented, declined, removed). Use this to build user-facing history views in your app. ### SDK logging [#sdk-logging] For development and debugging, the SDK exposes [diagnostic logging](/docs/holding/sdk-operations/sdk-logging) with configurable levels and callback hooks. Route SDK logs to your existing app telemetry or crash reporting tools. ## Next steps [#next-steps] Review the [frequently asked questions](/docs/holding/faq) for answers to common holding questions. # Platform requirements URL: /docs/holding/platform-requirements If you are integrating the MATTR Pi Holder SDK, ensure your app meets the minimum platform requirements before you begin. ## Platform requirements [#platform-requirements] If you're integrating the MATTR Pi Holder SDK, ensure your app meets these minimum requirements: | Platform | Minimum version | Language/framework | | ------------ | --------------------------- | ----------------------- | | iOS | iOS 15+ | Swift / Objective-C | | Android | Android 7+ (API 24) | Kotlin | | React Native | 0.78+ (iOS 15+, Android 7+) | JavaScript / TypeScript | For iOS 26+, the SDK supports the Digital Credentials API, which provides a native system overlay for credential presentation. On earlier iOS versions, the SDK uses standard redirect-based flows. ## Next steps [#next-steps] Next, review the [core capabilities to integrate](/docs/holding/core-capabilities). # Privacy in credential holding URL: /docs/holding/privacy Description: How MATTR's holder products keep credentials under the user's control, support selective disclosure, and minimize the ability to correlate usage across presentations. Holding is the part of the credential lifecycle that sits closest to the user. The holder's wallet is where credentials are stored, where consent is mediated, and where selective disclosure is enforced. The privacy properties of a credential ecosystem stand or fall on what the wallet does. This page describes how MATTR's holder products (the [MATTR Pi Holder SDKs](/docs/holding/sdk-overview) and the [MATTR GO](/docs/holding/go-hold/getting-started) white-label wallet) approach privacy. For the broader picture, see [Privacy in MATTR's architecture](/docs/concepts/privacy). ## The holder is in control [#the-holder-is-in-control] In a decentralized credential model, the holder is the party that controls the credential. The credential is delivered to the holder once at issuance and is then stored on the holder's device. The holder decides when to present it, to whom, and which attributes to release. MATTR's holder products implement this principle in three concrete ways: * **On-device storage:** Credentials are stored on the user's mobile device. Cryptographic keys bound to the credential are protected by platform secure storage (Secure Enclave on iOS, Keystore on Android). MATTR does not hold a server-side copy of the credential. * **Explicit consent:** Before any credential or attribute is released to a verifier, the wallet prompts the user to consent to the specific request. The user can see which attributes are being requested and decline if the request is excessive. * **Local enforcement of issuer policy:** Where the credential format and the issuer's policy support it, the wallet enforces the issuer's rules locally. For example, the wallet can refuse to release an attribute to a verifier that has not declared an acceptable purpose. ## Selective disclosure [#selective-disclosure] Selective disclosure is the mechanism that lets a holder reveal only the attributes a verifier needs, rather than the entire credential. It is one of the strongest privacy properties of the verifiable credential model. For a worked explanation of the concept and the business value, see [Selective disclosure](/docs/concepts/selective-disclosure). In practice, when a verifier sends a presentation request, the wallet: 1. Inspects the request to determine which attributes the verifier is asking for. 2. Identifies which of the holder's stored credentials can satisfy the request. 3. Shows the user the specific attributes the verifier has requested, along with the verifier's identity where available. 4. Asks the user to consent to release those attributes. 5. Produces a cryptographic proof that reveals only the consented attributes, while still proving the credential is signed by the issuer. The verifier never sees the attributes that were not requested. The proof is mathematically binding: the verifier can be confident that the disclosed attributes came from a credential signed by the issuer, without seeing anything else. ## Minimizing correlation across presentations [#minimizing-correlation-across-presentations] A separate concern from "what data does this verifier see" is "can multiple verifiers (or the same verifier over time) link their interactions with the same holder." This is the unlinkability problem, and it matters in any ecosystem where credentials are presented frequently. MATTR's holder products approach this in several ways: * **Credential formats that support selective disclosure:** mDocs let the holder release only the attributes a verifier needs, reducing the surface available for correlation. * **Ephemeral session keys for proximity presentation:** When a credential is presented in person using BLE (as defined in ISO/IEC 18013-5), a fresh session key is derived for every interaction. The key is unique to that session and discarded when the session ends. See [Proximity presentation](/docs/holding/proximity-presentation-overview) for the full engagement flow. * **Issuer support for short-lived credentials:** Where the use case allows, issuers can issue credentials that expire frequently and are refreshed in the background. This reduces the value of any single credential as a long-term correlator. Some correlation risk is inherent to any system where a holder presents the same unique attribute to multiple parties. If a holder presents the same driver licence number to two different verifiers, those verifiers can correlate their records if they choose to share them. The architecture cannot prevent this; verifier obligations and trust framework rules do that work. ## What MATTR's Holder SDKs do NOT do [#what-mattrs-holder-sdks-do-not-do] The decentralized model gives the wallet a privileged position. It sees every credential the holder receives and every presentation the holder makes. To preserve the architecture's privacy properties, MATTR's holder products explicitly do not: * Report verification activity back to MATTR or to a central server. * Transmit credential content to MATTR's infrastructure. * Build a server-side profile of the user across credentials and presentations. The mobile applications use a small set of backend services for legitimate operational purposes (license validation, application analytics about device type and OS version for compatibility and billing, error monitoring). These services do not receive credential content and are listed explicitly in the published sub-processor list at [/docs/resources/terms/data-sub-processors](/docs/resources/terms/data-sub-processors). ## Practical guidance for wallet integrators [#practical-guidance-for-wallet-integrators] If you are building a wallet using the MATTR Pi Holder SDKs or customizing MATTR GO, the following principles support a privacy-preserving holder experience: * Surface the verifier's identity and the specific attributes requested before asking for consent. Users cannot exercise control over what they cannot see. * Default consent UI to releasing the minimum required attributes. Let users opt into releasing more only if they choose. * Avoid logging or analytics that captures the attributes released in a presentation. Aggregate counts are fine; per-presentation attribute logs are not. * If your deployment supports holder analytics, make this opt-in and disclose it clearly in your privacy notice. * Be transparent with users about what device telemetry your wallet sends. The MATTR holder backend collects device model, OS version, and key attestation data for compatibility and licensing purposes. This is documented and should be reflected in the deployment's user-facing privacy notice. ## Frequently asked questions [#frequently-asked-questions] ### Where do credentials live when a user holds them? [#where-do-credentials-live-when-a-user-holds-them] Credentials live in the holder's wallet on their mobile device, protected by the platform's secure storage (Secure Enclave on iOS, Keystore on Android). The credential is not stored on a MATTR server. ### Does the issuer know when a credential is presented? [#does-the-issuer-know-when-a-credential-is-presented] No. The cryptographic design of the credentials MATTR supports lets a verifier confirm authenticity without contacting the issuer. The issuer is out of the verification loop and does not observe individual presentations. ### How does selective disclosure work in practice? [#how-does-selective-disclosure-work-in-practice] When a verifier requests information, the wallet shows the user which attributes the verifier is asking for. The user consents to release only the requested attributes. The credential format and cryptographic scheme produce a proof that reveals only those attributes while still proving the credential is signed by the issuer. ### Can two verifiers correlate the same holder across presentations? [#can-two-verifiers-correlate-the-same-holder-across-presentations] The credential formats MATTR supports are designed to limit unintended correlation, but some risk remains if the same long-lived attribute (for example, a unique licence number) is disclosed to multiple verifiers. Best practice is for verifiers to request only the minimum required attributes and for issuers to support short-lived credentials and rotating identifiers where the use case allows. ### What happens if a user loses their device? [#what-happens-if-a-user-loses-their-device] The credential is bound to keys held in platform secure storage on the lost device. Anyone gaining access to the device must also defeat the device's lock screen and any wallet-level authentication to use the credential. Issuers can revoke compromised credentials via the standard revocation flow. ## Related reading [#related-reading] * [Privacy in MATTR's architecture](/docs/concepts/privacy) * [Selective disclosure](/docs/concepts/selective-disclosure) * [Decentralized trust model](/docs/concepts/decentralized-trust-model) * [Proximity presentation](/docs/holding/proximity-presentation-overview) * [Remote presentation](/docs/holding/remote-presentation-overview) * [MATTR Pi Holder SDKs](/docs/holding/sdk-overview) # mDocs proximity presentation journey pattern URL: /docs/holding/proximity-presentation-journey-pattern This journey pattern assumes that the wallet is presenting a credential to a verifier using MATTR verification capabilities. However, the same pattern can be applied to any ISO/IEC 18013-5 compliant verifier. This journey pattern is used to verify an mDoc in-person via a [proximity verification workflow](/docs/verification/in-person-overview), as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). ## Overview [#overview] * **Issuance channel**: In-person, supervised * **Device/s**: Cross-device * **Formats**: mDocs * **Information assurance level**: High * **Identity assurance level**: High ## Journey flow [#journey-flow] mDocs In Person Presentation ## Architecture [#architecture] Enrolment architecture ### Establishing the connection [#establishing-the-connection] The user opens their digital wallet app on their mobile device and selects a credential they wish to present. They then establish a secure connection with the verifier in one of the following ways: The wallet app displays a QR code on the screen. The verifier application, which may be operated by a person or run on a self-service kiosk, scans this QR code using its device. Scanning the QR code initiates a secure Bluetooth Low Energy (BLE) connection between the two devices. On Android devices, the BLE connection can also be initiated via NFC tap. ### Sending a presentation request [#sending-a-presentation-request] Once the secure connection is established, the verifier application sends a presentation request to the wallet app. This request includes: * The specific information being requested (e.g. claims or attributes from a credential). * The identity of the verifier making the request. * The required assurance level for the data. * Instructions on how and where the response should be returned. ### Reviewing the presentation request [#reviewing-the-presentation-request] The wallet app processes the presentation request and identifies any matching credentials stored on the device. It presents this information to the user, showing: * Which credential(s) can satisfy the request. * Exactly what data will be disclosed if the user proceeds. * Whether selective disclosure is available for that credential, allowing the user to share only the requested claims. * Whether the verifier is trusted, using information from a Digital Trust Service configured for the current trust network. ### Sharing the information [#sharing-the-information] If the user consents, the wallet app: * Creates a verifiable presentation using the selected credential and requested data. * Cryptographically signs the presentation. * Sends the signed presentation back to the verifier app over the BLE connection. ### Verifying the information [#verifying-the-information] The verifier application receives the presentation and performs a series of verification checks, including: * Validating the digital signature to confirm the data has not been tampered with. * Checking that the credential has not been revoked or suspended, using a revocation list (if applicable). * Verifying that the credential is currently valid, based on its “valid from” and “valid until” dates. * Ensuring the credential was issued by a trusted issuer, based on information retrieved from a Digital Trust Service. The issuer of the credential is not informed that the presentation has occurred. No data about the verifier, the context of use, or the interaction itself is shared with the issuer. The only interaction with the issuer is a potential call to an online revocation endpoint, if revocation checking is required. # Proximity presentation URL: /docs/holding/proximity-presentation-overview Proximity presentation is a process where a credential holder presents their credential directly to a verifier in a proximity-based interaction. This may involve showing the credential to a person using a verifier device or to a self-service kiosk. Engagement methods include scanning a QR code, using a secure reader, or leveraging other proximity technologies. ## mDocs [#mdocs] ### Overview [#overview] mDocs can be presented in-person using proximity based technologies such as Bluetooth Low Energy (BLE), and support offline verification, as defined in [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html). ### Presentation flow [#presentation-flow] Proximity (in-person) presentation comprises two phases: 1. [**Engagement phase**](#engagement-phase): The interaction begins with the two devices attempting to establish a secure communication channel for transferring data. In the context of mDocs, this is between the holder’s and the verifier’s devices. 2. [**Data retrieval phase**](#data-retrieval-phase): Once a secure communication channel is established, data can be securely requested and exchanged between the two devices. For mDocs, this would be exchanging various data objects required for the verification workflow. #### Engagement phase [#engagement-phase] The engagement phase requires two mobile devices to: * Become aware of one another: Enabling the holder’s device to explicitly share engagement information with a verifier's device they wish to interact with. * Establish a secure communication channel: As wireless technologies are essentially insecure, additional security layers are required to prevent third parties from eavesdropping transactions. The [ISO/IEC 18013-5:2021 standard](https://www.iso.org/standard/69084.html) defines this layer, and it is quite similar to the usage of Transport Layer Security (TLS) for internet based protocols. Engagement phase ##### Holder generates a QR code [#holder-generates-a-qr-code] This phase is always initiated by the holder, who generates a QR code to be read by the verifier. While it is true that any device can read this QR code, the assumption is that the holder will only show the QR code to a verifier they trust and wish to share information with. This QR code does not include any claims, credentials, or personal information. It only contains information required to establish a secure communication channel: * Wireless communication protocols supported by the holder. * How the verifier can connect with the holder. * Ephemeral session public key. * Additional feature negotiations. ##### Verifier returns session key [#verifier-returns-session-key] Assuming the verifier device supports the same protocol requirements, it will generate its own ephemeral session public key, and attempt to establish a secure connection via a hand-shake response. The verifier response also includes an encrypted presentation request. However, for clarity purposes this step is described as part of the data retrieval phase. ##### Secure communication established [#secure-communication-established] As the two devices connect, a unique session transcript is created and used alongside the holder and verifier keys to derive a unique session key that is used to encrypt any exchanged data. This session key is unique for every session and removed when the session is terminated. Any attempt to use the same session key in a different session would fail, even if the session is between the same two devices. The holder will use the session key to decrypt the message (e.g. request from the verifier) and encrypt the responses (e.g. shared mDocs). #### Data retrieval phase [#data-retrieval-phase] Once a secure connection is established, the following flow takes place: Data phase Regardless of the device engagement technology used (QR Code/NFC), all data exchanged as part of the data retrieval phase is shared securely via BLE. ##### Request [#request] The verifier sends a presentation request. This details what type of information they wish to verify. Note that the encrypted presentation request is actually sent alongside the verifier ephemeral public key during the last step of the engagement phase. However, for clarity purposes this step is described as part of the data retrieval phase. ##### Consent [#consent] The holder receives the request and asks for the user’s consent to share the information. When applying selective disclosure, the holder device should show the user which of their mDocs include the required information, and enable the user to select which one to share. ##### Response [#response] When the user agrees to share the information, a presentation is sent back to the verifier with all of the information the user has consented to share. ##### Verification [#verification] The verifier will check the presentation, its signature and its issuer to complete the verification workflow with a verified/rejected conclusion. # Learn how to build an application that can present an mDoc via a proximity workflow URL: /docs/holding/proximity-presentation-tutorial ## Introduction [#introduction] In this tutorial you will use the [mDocs Holder SDKs](/docs/holding/sdk-overview) to build an application that can present a claimed [mDoc](/docs/concepts/mdocs) to a verifier that supports proximity verification as per [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html). Tutorial Workflow 1. The user launches the wallet application and generates a QR code. 2. The verifier scans the QR code, connects with the wallet and requests an mDoc for verification. 3. The wallet displays matching credentials to the user and asks for consent to share them with the verifier. 4. The verifier receives the wallet's response and verifies the provided credential. The result will look something like this: ## Prerequisites [#prerequisites] Before you get started, let's make sure you have everything you need. ### Prior knowledge [#prior-knowledge] * The verification workflow described in this tutorial is based on the [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) standard. If you are unfamiliar with this standard, refer to the following resources for more information: * What are [mDocs](/docs/concepts/mdocs)? * What is [credential verification](/docs/verification)? * Breakdown of the [proximity presentation workflow](/docs/verification/in-person-overview). * We assume you have experience developing applications in the relevant programming languages and frameworks (Swift for iOS, Kotlin for Android and TypeScript for React Native). If you need to get a holding solution up and running quickly with minimal development resources and in-house domain expertise, [talk to us](http://mattr.global/contact-us) about our white-label [MATTR GO Hold app](https://mattr.global/platforms/go) which might be a good fit for you. ### Prerequisite tutorial [#prerequisite-tutorial] * You must complete the [Claim a credential tutorial](/docs/holding/credential-claiming-tutorial) and claim the mDoc provided in the tutorial. * This application is used as the base for the current tutorial. ### Testing devices [#testing-devices] As this tutorial implements a proximity presentation workflow, you will need two separate physical devices to test the end-to-end result: * Holder device: * Supported iOS device to run the built application on, setup with: * Biometric authentication. * Bluetooth access. * Available internet connection. * Verifier device: * Android/iOS device with an installed verifier application. We recommend downloading and using the [MATTR GO Verify](/docs/verification/go-verify/getting-started) example app. * Setup with Bluetooth access. * Holder device: * Supported Android device to run the built application on, setup with: * Biometric authentication (Face recognition, fingerprint recognition). * Bluetooth access and Bluetooth turned on. * Available internet connection. * [USB debugging](https://developer.android.com/studio/debug/dev-options#enable) enabled. * Verifier device: * Android/iOS mobile device with an installed verifier application. We recommend downloading and using the [MATTR GO Verify](/docs/verification/go-verify/getting-started) example app. * Setup with Bluetooth access. * Holder device: * Supported iOS and/or Android device to run the built application on, setup with: * Biometric authentication. * Bluetooth access and Bluetooth turned on. * Available internet connection. * [USB debugging](https://developer.android.com/studio/debug/dev-options#enable) enabled (Android only). * Verifier device: * Android/iOS device with an installed verifier application. We recommend downloading and using the [MATTR GO Verify](/docs/verification/go-verify/getting-started) example app. * Setup with Bluetooth access and Bluetooth turned on. Got everything? Let's get going! ## Tutorial steps [#tutorial-steps] To enable a user to present a stored [mDoc](/docs/concepts/mdocs) to a verifier via a [proximity presentation workflow](/docs/verification/in-person-overview), you will build the following capabilities into your wallet application: 1. Create a QR code for the verifier to scan and establish a secure connection. 2. Receive and handle a presentation request from the verifier. 3. Send a matching mDoc presentation to the verifier. ### Step 1: Create a QR code for the verifier to scan [#step-1-create-a-qr-code-for-the-verifier-to-scan] Tutorial Workflow The first capability you need to build is to establish a secure communication channel between the verifier and holder devices. As defined in ISO/IEC 18130-5:2021, a [proximity presentation workflow](/docs/verification/in-person-overview) is always initiated by the holder (wallet user), who must create a QR code for the verifier to scan in order to initiate the [device engagement phase](/docs/verification/in-person-overview#engagement-phase). To achieve this, your wallet application needs a UI element for the user to interact with and trigger device engagement by calling the SDK's `createProximityPresentationSession` method. 1. Open the project that you built as part of the [Claim a credential tutorial](/docs/holding/credential-claiming-tutorial). 2. Open the `ContentView` file and add the following code under the `// Proximity Presentation - Step 1.2: Create deviceEngagementString and proximityPresentationSession variables` comment to create new variables to hold the [device engagement string](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/proximitypresentationsession/deviceengagement) and the [proximity presentation session](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/proximitypresentationsession). ```swift title="ContentView" var deviceEngagementString: String? var proximityPresentationSession: ProximityPresentationSession? ``` 3. Replace the `print` statement under the `// Proximity Presentation - Step 1.3: Create function to create a proximity presentation session and generate QR code` comment with the following code to call the SDK's [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/createproximitypresentationsession\(onrequestreceived:onconnected:onsessionterminated:deviceauthenticationoption:blemode:\)) method when the user selects the **Create QR Code** button: ```swift title="ContentView" Task { @MainActor in do { proximityPresentationSession = try await mobileCredentialHolder.createProximityPresentationSession( onRequestReceived: onRequestReceived(_:error:), bleMode: .mDocPeripheralServer ) deviceEngagementString = proximityPresentationSession?.deviceEngagement } catch { print(error) } } ``` * `bleMode`: Many modern POS terminals do not support BLE server mode. To maximize compatibility when these terminals are used as mDoc verifiers, we recommend starting the presentation session in `mDocPeripheralServer` mode. In this mode, the *holder* acts as the **BLE peripheral server**, while the *verifier* acts as the **BLE central client**. At this stage the project won't compile because you need to update the signature of the `func onRequestReceived`. Don't worry, we'll get to that in the next step. 4. Replace the `func` statement below the `// Proximity Presentation - Step 1.4: Update function signature` comment with the following code to update the function's signature (don't change the function body for now): ```swift title="ContentView" func onRequestReceived(_ mobileCredentialRequests: [(request: MobileCredentialRequest, matchedMobileCredentials: [MobileCredentialMetadata])]?, error: Error?) { ``` 5. Replace the `EmptyView()` statement under the `// Proximity Presentation - Step 1.5: Add button to generate QR code` comment and add the following code to create a button that will generate the QR code when the user selects it: ```swift title="ContentView" Button { viewModel.createDeviceEngagementString() // Navigates user to presentCredentialsView, once the string has been created. viewModel.navigationPath.append(NavigationState.presentCredentials) } label: { Text("Present Credentials") } ``` Now, when the user selects the **Create QR Code button**, the application will call the SDK's [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/createproximitypresentationsession\(onrequestreceived:onconnected:onsessionterminated:deviceauthenticationoption:blemode:\)) method, which returns a [`ProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/proximitypresentationsession) instance that includes a `deviceEngagement` string in `base64` format: ``` "mdoc:owBjMS4wAYIB2BhYS6QBAiABIVgghaBYJe7KSqcEolhmnIJaYJ2AIevkKbEy5xP7tkwlqAwiWCAMGCGe6uFI2hKeghb59h_K4hPV-Ldq6vnaxsRiySMH9gKBgwIBowD0AfULUKRoj0ZH60Qco-m0k97qRSQ" ``` The `deviceEngagement` string is always prefixed with `mdoc:` and contains the information required to establish a secure connection between the two devices, including: * Wireless communication protocols supported by the holder. * How the verifier can connect with the holder. * Ephemeral public key which is unique to the current session. * Additional feature negotiations. Your app needs to convert this `deviceEngagement` string into a QR code and display it in the wallet UI for the verifier to scan. 6. Replace the `return nil` statement under the `// Proximity Presentation - Step 1.6: Generate QR code` comment with the following code to retrieve data from the `deviceEngagement` string and convert it into a QR code: ```swift title="ContentView" guard let filter = CIFilter(name: "CIQRCodeGenerator") else { return nil } filter.setValue(data, forKey: "inputMessage") guard let ciimage = filter.outputImage else { return nil } let transform = CGAffineTransform(scaleX: 10, y: 10) let scaledCIImage = ciimage.transformed(by: transform) let uiimage = UIImage(ciImage: scaledCIImage) return uiimage.pngData() ``` 7. Replace the `EmptyView()` statement under the `// Proximity Presentation - Step 1.7: Create QR code view` comment with the following code to generate a QR Code and display it to the user: ```swift title="ContentView" VStack { Text("Scan to establish device engagement session") .font(.title3) Spacer() if let imageData = generateQRCode(data: viewModel.deviceEngagementString?.data(using: .utf8) ?? Data()), let image = UIImage(data: imageData) { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) } Spacer() } ``` 8. [Run](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device) the app and select the **Present Credentials** button. You should see a result similar to the following: * As the user selects the **Present Credentials** button, they are navigated to the new view, and a new proximity presentation session is created. * Once the session is created, the application generates and displays a QR code that can be scanned by a verifier device to establish a secure proximity communication channel over Bluetooth. * Once the QR code is displayed, the [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/proximitypresentationsession) function enters a listening state, ready to establish a Bluetooth connection with a verifier application that scans the QR Code. * When a verifier application scans the QR code, the devices will automatically exchange public keys to establish a secure communication channel, enabling the verifier to send a presentation request, which details: * What credentials are required. * What specific claims are required from these credentials. Tutorial Workflow 1. In your [Claim a tutorial](/docs/holding/credential-claiming-tutorial) application project, create a new file named `PresentationQrScreen.kt` and add the following code: ```kotlin title="PresentationQrScreen.kt" package com.example.holdertutorial import android.app.Activity import android.graphics.Bitmap import android.graphics.Color import android.widget.Toast import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.unit.IntSize import androidx.navigation.NavController import com.google.zxing.BarcodeFormat import com.google.zxing.EncodeHintType import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel import com.journeyapps.barcodescanner.BarcodeEncoder import global.mattr.mobilecredential.holder.MobileCredentialHolder import global.mattr.mobilecredential.holder.ProximityPresentationSession import global.mattr.mobilecredential.holder.datatransport.bluetooth.BleMode import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @Composable fun PresentationQrScreen(activity: Activity, navController: NavController) { var containerSize by remember { mutableStateOf(IntSize.Zero) } var session: ProximityPresentationSession? by remember { mutableStateOf(null) } var qrCode: Bitmap? by remember { mutableStateOf(null) } LaunchedEffect(Unit) { // Step 1.4: Create a Proximity presentation session } LaunchedEffect(session, containerSize) { // Step 1.6: Generate a QR code } Box( modifier = Modifier .aspectRatio(1f) .onSizeChanged { containerSize = it } ) { qrCode?.let { Image( bitmap = it.asImageBitmap(), contentDescription = "A QR Code", modifier = Modifier.fillMaxSize() ) } } } // Step 1.5: Create function to generate QR Code from String ``` We will copy and paste different code snippets into specific locations in this codebase to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step. We recommend copying and pasting the comment text (e.g. `// Proximity Presentation - Step 1.2: Add "QR" screen call`) to easily locate it in the code. 2. Return to your `MainActivity` file and add the following code under the `// Proximity Presentation - Step 1.2: Add "QR Presentation" screen call` comment to connect the created composable to the navigation graph: ```kotlin title="MainActivity.kt" PresentationQrScreen(this@MainActivity, navController) ``` 3. Still in the `MainActivity` file file, add the following code under the `// Proximity Presentation - Step 1.3: Add button for starting the credentials presentation workflow` comment to add a button that navigates the user to the `PresentationQrScreen`: ```kotlin title="MainActivity.kt" Button(onClick = { navController.navigate("presentationQr") }, Modifier.fillMaxWidth()) { Text("Present Credentials") } ``` 4. Open the `PresentationQrScreen.kt` file and add the following code under the `// Step 1.4: Create a Proximity presentation session` comment to call the [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/create-proximity-presentation-session.html) function when the screen is loaded: ```kotlin title="PresentationQrScreen.kt" session = MobileCredentialHolder.getInstance().createProximityPresentationSession( activity, bleMode = BleMode.MDocPeripheralServer, onRequestReceived = { _, requests, e -> // Step 2.2: Handle the presentation request } ) ``` * `bleMode`: Many modern POS terminals do not support BLE server mode. To maximize compatibility when these terminals are used as mDoc verifiers, we recommend starting the presentation session in `mDocPeripheralServer` mode. In this mode, the *holder* acts as the **BLE peripheral server**, while the *verifier* acts as the **BLE central client**. Now, when the `PresentationQrScreen` is loaded, the application will call the SDK's [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/create-proximity-presentation-session.html) function, which returns a [`ProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-proximity-presentation-session/index.html) instance that includes a `deviceEngagement` string in `base64` format: ``` "mdoc:owBjMS4wAYIB2BhYS6QBAiABIVgghaBYJe7KSqcEolhmnIJaYJ2AIevkKbEy5xP7tkwlqAwiWCAMGCGe6uFI2hKeghb59h_K4hPV-Ldq6vnaxsRiySMH9gKBgwIBowD0AfULUKRoj0ZH60Qco-m0k97qRSQ" ``` This `deviceEngagement` string is always prefixed with `mdoc:` and contains the information required to establish a secure connection between the holder and verifier devices, including: * Wireless communication protocols supported by the holder. * How the verifier can connect with the holder. * Ephemeral public key which is unique to the current session. * Additional feature negotiations. Your application needs to convert this `deviceEngagement` string into a QR code and display it in the `PresentationQrScreen` for the verifier to scan. 5. Add the following code under the `// Step 1.5: Create function to generate QR Code from String` comment to add a function that converts a `String` to a QR code rendered as a `Bitmap` image: ```kotlin title="PresentationQrScreen.kt" private fun String.toQrCode(size: IntSize): Bitmap? { if (this.isEmpty() || size == IntSize.Zero) return null val (width, height) = size return Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565).apply { val hints = mapOf(EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.M) val encoded = BarcodeEncoder() .encode(this@toQrCode, BarcodeFormat.QR_CODE, width, height, hints) for (x in 0 until width) { for (y in 0 until height) { setPixel(x, y, if (encoded[x, y]) Color.BLACK else Color.WHITE) } } } } ``` To generate the QR code, you need the `ProximityPresentationSession` to be established by the SDK and the container size to be calculated. 6. Add the following code under the `// Step 1.6: Generate a QR code` comment to call the new `toQrCode` function and generate the QR code when the `session` or `containerSize` state changes: ```kotlin title="PresentationQrScreen.kt" qrCode = session?.deviceEngagement?.toQrCode(containerSize) ``` 7. [Run](https://developer.android.com/studio/run#basic-build-run) the app, and select the **Present Credentials** button. You should see a result similar to the following: * As the user selects the **Present Credentials** button, they are navigated to the new `PresentationQrScreen`, and a new proximity presentation session is created. * Once the session is created, the application generates and displays a QR code that can be scanned by a verifier device to establish a secure proximity communication channel over Bluetooth. * Once the QR code is displayed, the [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/create-proximity-presentation-session.html?query=suspend%20fun%20createProximityPresentationSession\(activity:%20Activity,%20deviceAuthenticationOption:%20DeviceAuthenticationOption%20=%20DeviceAuthenticationOption.Signature,%20bleMode:%20BleMode%20=%20BleMode.MDocClientCentral,%20onRequestReceived:%20ProximityPresentationSession.OnRequestReceived,%20onConnected:%20ProximityPresentationSession.OnConnected?%20=%20null,%20onSessionTerminated:%20ProximityPresentationSession.OnSessionTerminated?%20=%20null\):%20ProximityPresentationSession) function enters a listening state, ready to establish a Bluetooth connection with a verifier application that scans the QR Code. * When a verifier application scans the QR code, the devices will automatically exchange public keys to establish a secure communication channel, enabling the verifier to send a presentation request, which details: * What credentials are required. * What specific claims are required from these credentials. Tutorial Workflow 1. In your project's `app` directory, create a new file named `proximity-presentation.tsx` and add the following scaffolding code to create a new screen that will display the generated QR code: ```ts title="app/proximity-presentation.tsx" // Step 2.2: Import Credential selector component import { useHolder } from "@/providers/HolderProvider"; import { type CreateProximityPresentationSessionOptions, type PresentationSessionSuccessRequest, createProximityPresentationSession, sendProximityPresentationResponse, terminateProximityPresentationSession, } from "@mattrglobal/mobile-credential-holder-react-native"; import { useRouter } from "expo-router"; import React, { useState, useCallback, useEffect } from "react"; import { Alert, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; import QRCode from "react-native-qrcode-svg"; export default function ProximityPresentation() { const router = useRouter(); const { isHolderInitialized } = useHolder(); const [error, setError] = useState(null); const [deviceEngagement, setDeviceEngagement] = useState( null, ); const [requests, setRequests] = useState< PresentationSessionSuccessRequest["request"] >([]); const [selectedCredentialIds, setSelectedCredentialIds] = useState< string[] >([]); const navigateToIndex = useCallback(() => { router.push("/"); }, [router]); const resetState = useCallback(() => { setDeviceEngagement(null); setRequests([]); setSelectedCredentialIds([]); }, []); const handleError = useCallback((message: string) => { setError(message); console.error(message); }, []); // Step 2.3: Add handleToggleSelection function if (!isHolderInitialized) { return ( Holder instance not found. Please restart the app and try again. ); } // Step 1.2: Add handleStartSession function // Step 1.6: Add terminateSession function // Step 3.1: Add handleSendResponse function return ( {error && {error}} {/* Step 1.3: Add fallback UI */} ); } const styles = StyleSheet.create({ container: { flex: 1, padding: 16, paddingBottom: 32 }, errorText: { color: "red", marginBottom: 10 }, infoText: { marginBottom: 10 }, qrContainer: { alignItems: "center", marginVertical: 20 }, button: { backgroundColor: "#007AFF", paddingVertical: 12, paddingHorizontal: 20, borderRadius: 8, alignItems: "center", marginTop: 20, }, buttonDanger: { backgroundColor: "#FF3B30", }, buttonText: { color: "white", fontSize: 16, fontWeight: "600", }, }); ``` This will serve as the basic structure for this capability. We will copy and paste different code snippets into specific locations in this codebase to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step. We recommend copying and pasting the comment text (e.g. `// Proximity Presentation - Step 1.2: Add Proximity Presentation button`) to easily locate it in the code. 2. Add the following code under the `// Step 1.2: Add handleStartSession function` comment to create a new `handleStartSession` function that calls the SDK's [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/createProximityPresentationSession.html) function: ```ts title="app/proximity-presentation.tsx" const handleStartSession = useCallback(async () => { try { const options: CreateProximityPresentationSessionOptions = { bleMode: "MDocPeripheralServer", onRequestReceived: (data) => { if ("error" in data) { handleError(`Request received error: ${data.error.message}`); return; } setRequests(data.request); }, onSessionTerminated: (error) => { if (error) { console.log(`Session terminated with error: ${error.message}`); } resetState(); navigateToIndex(); }, }; const result = await createProximityPresentationSession(options); if (result.isErr()) { throw new Error( `Error creating proximity session: ${result.error.message || result.error.type}`, ); } setDeviceEngagement(result.value.deviceEngagement); } catch (err: unknown) { handleError(err instanceof Error ? err.message : String(err)); } }, [handleError, resetState, navigateToIndex]); // Automatically start session when component mounts useEffect(() => { handleStartSession(); }, [handleStartSession]); ``` * `bleMode`: Many modern POS terminals do not support BLE server mode. To maximize compatibility when these terminals are used as mDoc verifiers, we recommend starting the presentation session in `MDocPeripheralServer` mode. In this mode, the *holder* acts as the **BLE peripheral server**, while the *verifier* acts as the **BLE central client**. 3. Add the following code under the `{/* Step 1.3: Add fallback UI */}` comment to display a message to the user while the proximity session is being established: ```ts title="app/proximity-presentation.tsx" { !deviceEngagement ? ( <> Waiting for session to establish... ) : ( <>{/* Step 1.4: Display QR code */} ); } ``` Now, when the user navigates to the **Proximity Presentation** screen, the `handleStartSession` function is called and creates a new proximity presentation session by calling the SDK's [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/createProximityPresentationSession.html) function. This SDK function returns a [`ProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/ProximityPresentationSession.html) instance that includes a `deviceEngagement` string in `Base64` format: ``` "mdoc:owBjMS4wAYIB2BhYS6QBAiABIVgghaBYJe7KSqcEolhmnIJaYJ2AIevkKbEy5xP7tkwlqAwiWCAMGCGe6uFI2hKeghb59h_K4hPV-Ldq6vnaxsRiySMH9gKBgwIBowD0AfULUKRoj0ZH60Qco-m0k97qRSQ" ``` The `deviceEngagement` string is always prefixed with `mdoc:` and contains the information required to establish a secure connection between the two devices, including: * Wireless communication protocols supported by the holder. * How the verifier can connect with the holder. * Ephemeral public key which is unique to the current session. * Additional feature negotiations. Your app needs to convert this `deviceEngagement` string into a QR code and display it so that the verifier can scan it. 4. Add the following code under the `{/* Step 1.4: Display QR code */}` comment to use the `deviceEngagement` variable to generate a QR code and display it: ```ts title="app/proximity-presentation.tsx" <> A proximity session is active. {requests.length === 0 ? ( {/* Step 1.7: Add Terminate session button */} ) : ( <> {/* Step 2.4: Display request details */} {/* Step 3.2: Send response */} )} ``` 5. Open the `index.tsx` file in the `app` directory and add the following code under the `{/* Proximity Presentation - Step 1.5: Add Proximity Presentation button */}` comment to add a new button to navigate to the new `proximity-presentation` screen created in the previous steps: ```ts title="app/index.tsx" router.replace("/proximity-presentation")} > Present Credential ``` Proximity verification workflows require the use of Bluetooth to establish a secure communication channel with a verifier device. To enable this feature for iOS applications, you need to adjust the app configuration to include the necessary permissions. To properly manage proximity sessions, we will create two functions that allow the application to gracefully handle scenarios where users navigate away from the screen before completing the verification process. 6. Add the following code under the `{/* Proximity Presentation - Step 1.7: Add terminateSession function */}` comment to create the `terminateSession` and `handleTerminateSession` functions: ```ts title="app/proximity-presentation.tsx" const terminateSession = useCallback(async () => { try { await terminateProximityPresentationSession(); resetState(); navigateToIndex(); } catch (err: unknown) { const message = err instanceof Error ? err.message : String(err); handleError(`Failed to terminate session: ${message}`); } }, [resetState, handleError, navigateToIndex]); const handleTerminateSession = useCallback(async () => { await terminateSession(); }, [terminateSession]); useEffect(() => { return () => { if (deviceEngagement) { terminateSession(); } }; }, [deviceEngagement, terminateSession]); ``` 7. Add the following code under the `{/* Proximity Presentation - Step 1.7: Add Terminate session button */}` comment to create a button that will call the `handleTerminateSession` function when selected: ```ts title="app/proximity-presentation.tsx" Terminate Session ``` The `handleTerminateSession` function is called when the user selects the **Terminate Session** button, and it displays an alert to confirm that the session has been terminated. The `useEffect` hook acts as a cleanup function when the component is unmounted or if the user navigates away from the screen. If a proximity session is active, it calls the `terminateSession` function to end the session. The `terminateSession` function calls the SDK's `terminateProximityPresentationSession` method to end the current proximity presentation session and reset the application state. 8. Run the app and select the **Present Credential** button. You should see a result similar to the following: * As the user selects the **Present Credential** button a new proximity presentation session is created. * Once the session is created, the application retrieves the `deviceEngagement` string and uses it to generate and display a QR code that can be scanned by a verifier device to establish a secure proximity communication channel over Bluetooth. * Once the QR code is displayed, the [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/createProximityPresentationSession.html) function enters a listening state, ready to establish a Bluetooth connection with a verifier application that scans the QR Code. * When a verifier application scans the QR code, the devices will automatically exchange public keys to establish a secure communication channel, enabling the verifier to send a presentation request, which details: * What credentials are required. * What specific claims are required from these credentials. We will implement the logic that handles this presentation request in the next step. Tutorial Workflow ### Step 2: Handle the presentation request [#step-2-handle-the-presentation-request] Tutorial Workflow The `createProximityPresentationSession` function can handle three types of events: * `onConnected`: When a secure connection is established. * `onSessionTerminated`: When a secure connection is terminated for whatever reason. * `onRequestReceived`: When a presentation request is received from the verifier. `onConnected` and `onSessionTerminated` are optional events and will not be implemented in this tutorial. You can find more information about these events in the reference documentation for the [iOS](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk), [Android](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/index.html) and [React Native](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html) SDKs. When the SDK receives a presentation request from a verifier, an `onRequestReceived` event is triggered. The SDK then checks its credential storage for any credentials that match the information defined in this request. The application then needs to: * Present these matching credentials to the user. * Present what claims will be shared with the verifier. * Provide a UI element for the user to consent sharing this information with the verifier. The following step is also included in the [Online presentation tutorial](/docs/holding/remote-presentation-tutorial). If you had already completed this tutorial you may skip to step 2. 1. Add the following code under the `// Proximity and Online Presentation: Create variables for credential presentations` comment to create the following variables: ```swift title="ContentView" var matchedCredentials: [MobileCredential] = [] var matchedMetadata: [MobileCredentialMetadata] = [] var credentialRequest: [MobileCredentialRequest] = [] ``` * `matchedCredentials` : Holds stored credentials that match the credential request. * `matchedMetadata` : Holds metadata of credentials that match the credential request. * `credentialRequest`: Holds the credentials that were requested for verification. Once the application receives a credential request from a verifier (`onRequestReceived` event), the [createProximityPresentationSession](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/createproximitypresentationsession\(onrequestreceived:onconnected:onsessionterminated:deviceauthenticationoption:blemode:\)) function will return a `mobileCredentialRequests` array that includes pairs of credentials requested by the verifier ([MobileCredentialRequest](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialrequest)) and the metadata of stored credentials that match the requested information ([MobileCredentialMetadata](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialmetadata)). 2. Replace the `print` statements under the `// Proximity Presentation - Step 2.2: Store credential requests and matched credentials` comment with the following code to store the values from the `mobileCredentialRequests` array in the `matchedMetadata` and `credentialRequest` variables: ```swift title="ContentView" Task { @MainActor in matchedMetadata = mobileCredentialRequests? .flatMap { $0.matchedMobileCredentials } .compactMap { $0 } ?? [] credentialRequest = mobileCredentialRequests? .compactMap { $0.request } .compactMap { $0 } ?? [] // Navigate to presentation view if there are no errors if error == nil { navigationPath.append(NavigationState.proximityPresentation) } else { print(error!) } } ``` The following two steps are also included in the [Online presentation tutorial](/docs/holding/remote-presentation-tutorial). If you had already completed this tutorial you may skip to step 5. 3. Replace the `print` statement under the `// Proximity and Online Presentation: Retrieve a credential from storage` comment with the following code create a function that uses the SDK's [getCredential](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/getcredential\(credentialid:fetchupdatedstatuslist:\)) method to retrieve a credential from the application storage: ```swift title="ContentView" Task { do { let credential = try await mobileCredentialHolder.getCredential(credentialId: id) matchedCredentials.append(credential) } catch { print(error) } } ``` The [MobileCredentialMetadata](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialmetadata) object does not include the values of claims included in the credential. To display these values, the above function calls the SDK's [getCredential](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/getcredential\(credentialid:fetchupdatedstatuslist:\)) method with the `id` property of the [MobileCredentialMetadata](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialmetadata). 4. Create a new file called `PresentCredentialsView.swift` and paste the following code to create a view to display credential requests and matching credentials stored in the application: ```swift title="PresentCredentialsView" import MobileCredentialHolderSDK import SwiftUI struct PresentCredentialsView: View { var viewModel: PresentCredentialsViewModel @State var selectedID: String? var body: some View { ScrollView { VStack(alignment: .leading, spacing: 20) { Text("Requested Documents") .font(.headline) .padding(.leading) ForEach(viewModel.requestedDocuments, id: \.docType) { requestedDocument in DocumentView(viewModel: DocumentViewModel(from: requestedDocument)) } Text("Matched Credentials") .font(.headline) .padding(.leading) ForEach(viewModel.matchedMetadata, id: \.id) { matchedMetadata in VStack(alignment: .leading, spacing: 10) { if let matchedCredential = viewModel.matchedMobileCredential(id: matchedMetadata.id) { DocumentView(viewModel: DocumentViewModel(from: matchedCredential)) .padding(.vertical) .background(selectedID == matchedMetadata.id ? Color.blue.opacity(0.2) : Color.clear) .onTapGesture { guard selectedID != matchedMetadata.id else { selectedID = nil return } selectedID = matchedMetadata.id } Button("Hide claim values") { viewModel.matchedCredentials.removeAll(where: { $0.id == matchedMetadata.id }) } .frame(maxWidth: .infinity, alignment: .center) } else { DocumentView(viewModel: DocumentViewModel(from: matchedMetadata)) .padding(.vertical) .background(selectedID == matchedMetadata.id ? Color.blue.opacity(0.2) : Color.clear) .onTapGesture { guard selectedID != matchedMetadata.id else { selectedID = nil return } selectedID = matchedMetadata.id } Button("Show claim values") { viewModel.getCredentialAction(matchedMetadata.id) } .frame(maxWidth: .infinity, alignment: .center) } } } } } if selectedID != nil { Button("Send Response") { viewModel.sendCredentialAction(selectedID!) } .buttonStyle(.borderedProminent) .clipShape(Capsule()) .frame(maxWidth: .infinity, alignment: .center) } } } // MARK: PresentCredentialsViewModel class PresentCredentialsViewModel { @Binding var requestedDocuments: [MobileCredentialRequest] @Binding var matchedCredentials: [MobileCredential] @Binding var matchedMetadata: [MobileCredentialMetadata] var getCredentialAction: (String) -> Void var sendCredentialAction: (String) -> Void init( requestedDocuments: Binding<[MobileCredentialRequest]>, matchedCredentials: Binding<[MobileCredential]>, matchedMetadata: Binding<[MobileCredentialMetadata]>, sendCredentialAction: @escaping (String) -> Void, getCredentialAction: @escaping (String) -> Void ) { self._requestedDocuments = requestedDocuments self._matchedCredentials = matchedCredentials self._matchedMetadata = matchedMetadata self.sendCredentialAction = sendCredentialAction self.getCredentialAction = getCredentialAction } func matchedMobileCredential(id: String) -> MobileCredential? { matchedCredentials.first(where: { $0.id == id }) } } ``` The `PresentCredentialsView` view is used to: * Display requested information. * Display stored credentials that include the requested information. * Enable the user to provide consent to sharing the requested information with the verifier. The `PresentCredentialsViewModel` object is used to reference values from a credential request. It takes two closures in its initializer: * `getCredentialAction: (String) -> Void` is used to display claim values. * `sendCredentialAction: (String) -> Void` is used to send a credential response to the verifier once the user selected a credential and provided consent by selecting the **Send Response** button. 5. Return to the `ContentView` file and replace the `EmptyView()` statement under the `// Proximity Presentation - Step 2.5: Display proximity presentation view` comment with the new view that you created: ```swift title="ContentView" PresentCredentialsView( viewModel: PresentCredentialsViewModel( requestedDocuments: $viewModel.credentialRequest, matchedCredentials: $viewModel.matchedCredentials, matchedMetadata: $viewModel.matchedMetadata, sendCredentialAction: viewModel.sendProximityPresentationResponse(id:), getCredentialAction: viewModel.getCredential(id:) ) ) ``` 6. [Run](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device) the app, select the **Present Credentials** button and then select the **Create QR code** button. Next, use your testing verifier app to scan the presented QR code and send a presentation request. You should see a result similar to the following: As the user selects the **Present Credentials** button, the wallet generates and displays a QR code. When a compliant verifier app scans the QR code, a secure communication channel is established via Bluetooth. The verifier then sends a presentation request, which is displayed to the user on their digital wallet, alongside any credentials they have stored in their wallet that match the request. When the user selects the **Show claim values** button, the SDK retrieves the corresponding credential and the application displays its claim values. When the user selects a credential, it is highlighted and the **Send Response** button appears at the bottom of the screen. 1. In your `MainActivity` file, add the following code under the `// Step 2.1: Add proximity presentation request variable` comment to create a new variable that will hold the presentation request information and share it between the screens: ```kotlin title="MainActivity.kt" var proximityPresentationRequest: ProximityPresentationSession.CredentialRequestInfo? = null ``` 2. Open the `PresentationQrScreen.kt` file and add the following code under the `// Step 2.2: Handle the presentation request` comment to navigate to a new screen when an `onRequestReceived` event is received. ```kotlin title="PresentationQrScreen.kt" if (e == null && !requests.isNullOrEmpty()) { // Using only the first request for simplicity SharedData.proximityPresentationRequest = requests.first() withContext(Dispatchers.Main) { navController.navigate("presentationSelectCredentials") } } else { val msg = "Error while retrieving the request" withContext(Dispatchers.Main) { Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show() } } ``` 3. Create a new file named `PresentationSelectCredentialsScreen.kt` and add the following code to create the new screen which displays matching credentials and enables the user to select which credential to share: ```kotlin title="PresentationSelectCredentialsScreen.kt" package com.example.holdertutorial import android.app.Activity import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import global.mattr.mobilecredential.holder.deviceretrieval.devicerequest.DataElements import global.mattr.mobilecredential.holder.deviceretrieval.deviceresponse.NameSpace import global.mattr.mobilecredential.holder.dto.MobileCredential import global.mattr.mobilecredential.holder.dto.MobileCredentialMetaData import global.mattr.mobilecredential.holder.MobileCredentialHolder import kotlinx.coroutines.launch @Composable fun PresentationSelectCredentialsScreen(activity: Activity) { val request = SharedData.proximityPresentationRequest ?: return var matchedCredentials by remember { mutableStateOf(request.matchedCredentials) } var selectedCredentialId by remember { mutableStateOf(matchedCredentials.first().id) } val coroutineScope = rememberCoroutineScope() Column(Modifier.verticalScroll(rememberScrollState())) { Text("REQUESTED DATA", style = MaterialTheme.typography.titleLarge) Card(Modifier.padding(vertical = 8.dp)) { Document(request.request.docType, request.request.namespaces.value.toUi()) } Spacer(Modifier.padding(12.dp)) Text("MATCHED CREDENTIALS", style = MaterialTheme.typography.titleLarge) Spacer(Modifier.padding(6.dp)) matchedCredentials.forEach { matchedCredential -> // Step 2.5: Display matching credentials and claims } // Step 3.2: Send response } } // Step 2.4: Create function to add values to claims private fun Map.toUi() = mapValues { (_, dataElements) -> dataElements.value.keys.toSet() } // Step 3.1: Create function to send the credential response ``` This code is very similar to the one used in the in the `OnlinePresentationScreen.kt` file in the [Online Presentation](/docs/holding/remote-presentation-tutorial) tutorial, to avoid creating dependencies between the tutorials. In your own project you can use the same components for both presentation workflows. The application retrieves information from the SDK's `ProximityPresentationSession` object and displays it to the user: * Credentials and claims included in the verification request are retrieved from the `docType` and `nameSpaces.value` properties and displayed in the *"REQUESTED INFORMATION"* area. * Available credentials that match the requested information are retrieved from the `matchedCredentials` property and displayed in the *"MATCHING CREDENTIALS"* area. 4. Add the following code under the `// Step 2.4: Create function to add values to claims` comment to create a new function that will enable displaying the values of the claims the user is about to share. ```kotlin title="PresentationSelectCredentialsScreen.kt" private fun List.withClaimValues( from: MobileCredential ): List = map { credential -> if (credential.id != from.id) { credential } else { credential.copy( claims = credential.claims.mapValues { (namespace, claims) -> claims.map { claim -> val claimValue = from.claims[namespace]?.get(claim) claimValue?.let { "$claim: ${it.toUiString()}" } ?: claim }.toSet() } ) } } ``` 5. Add the following code under the `// Step 2.5: Display matching credentials and claims` comment to display to the user what credentials and claims they are about to share with the verifier, as well as a button that enables the user to display the value of those claims: ```kotlin title="PresentationSelectCredentialsScreen.kt" val borderWidth = if (matchedCredential.id == selectedCredentialId) 4.dp else 0.dp Column( Modifier .clickable { selectedCredentialId = matchedCredential.id } .border(borderWidth, Color.Blue, RoundedCornerShape(16.dp)) .padding(8.dp) ) { Card(Modifier.fillMaxWidth()) { Document(matchedCredential.docType, matchedCredential.claims) } Button( onClick = { val credentialWithValues = MobileCredentialHolder.getInstance() .getCredential(matchedCredential.id, fetchUpdatedStatusList = false) matchedCredentials = matchedCredentials.withClaimValues(from = credentialWithValues) }, Modifier.fillMaxWidth() ) { Text("Show Values") } } Spacer(Modifier.padding(12.dp)) ``` 6. Back in the `MainActivity` file, add the following code under the `// Proximity Presentation - Step 2.6: Add "Select Credential" screen call` comment to connect the created composable to the navigation graph: ```kotlin title="MainActivity.kt" PresentationSelectCredentialsScreen(this@MainActivity) ``` 7. [Run](https://developer.android.com/studio/run#basic-build-run) the app, and select the **Present Credentials** button. Next, use your testing verifier app to scan the presented QR code and send a presentation request. You should see a result similar to the following: * As the user selects the **Present Credentials** button, they are navigated to the new `PresentCredentialsView` where the application generates and displays a QR code. * When a compliant verifier app scans the QR code, a secure communication channel is established via Bluetooth. * The verifier then sends a presentation request, which is displayed to the user alongside any credentials they have stored that match the request. The following step is also included in the [Online presentation tutorial](/docs/holding/remote-presentation-tutorial#handle-a-presentation-request). If you had already completed this tutorial and created the `RequestCredentialSelector.tsx` component, you may skip to step 2.2. 1. In the `app/components` directory, create a file named `RequestCredentialSelector.tsx` and add the following code: ```ts title="app/components/RequestCredentialSelector.tsx" import type { MobileCredentialMetadata, OnlinePresentationSession, } from "@mattrglobal/mobile-credential-holder-react-native"; import type React from "react"; import { FlatList, type ListRenderItem, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; type RequestCredentialSelectorProps = { requests: PresentationSessionSuccessRequest["request"]; selectedCredentialIds: string[]; onToggleSelection: (credentialId: string) => void; }; type RequestItem = PresentationSessionSuccessRequest["request"][number]; /** * Component that renders a list of credential requests and their matched credentials. * * @param props - The component props. * @param props.requests - The list of credential requests. * @param props.selectedCredentialIds - The list of selected credential IDs. * @param props.onToggleSelection - Callback function to toggle the selection of a credential. * @returns The rendered component. */ export default function RequestCredentialSelector({ requests, selectedCredentialIds, onToggleSelection, }: RequestCredentialSelectorProps) { const renderCredential: ListRenderItem = ({ item: cred, }) => { const isSelected = selectedCredentialIds.includes(cred.id); return ( onToggleSelection(cred.id)} > {isSelected && } {cred.branding?.name ?? "Credential"} ({cred.id}) ); }; const renderRequest: ListRenderItem = ({ item }) => ( Request Details {typeof item.request === "object" ? JSON.stringify(item.request, null, 2) : item.request} Matched Credentials: cred.id} renderItem={renderCredential} style={styles.credentialsList} contentContainerStyle={styles.credentialsListContent} /> ); return ( idx.toString()} renderItem={renderRequest} style={styles.requestsList} contentContainerStyle={styles.requestsListContent} /> ); } const styles = StyleSheet.create({ requestsList: { flex: 1, }, requestsListContent: { paddingBottom: 10, }, requestContainer: { backgroundColor: "#f0f0f0", padding: 10, borderRadius: 8, marginBottom: 15, }, requestInfo: { fontStyle: "italic", fontSize: 12, marginBottom: 10, }, label: { fontWeight: "bold", fontSize: 14, textTransform: "uppercase", marginBottom: 5, }, credentialsList: { maxHeight: 200, }, credentialsListContent: { paddingBottom: 10, }, credentialItem: { flexDirection: "row", alignItems: "center", marginBottom: 8, paddingVertical: 4, }, selectionIndicator: { height: 20, width: 20, borderRadius: 10, borderWidth: 1, borderColor: "#000", alignItems: "center", justifyContent: "center", marginRight: 8, }, selectionInner: { height: 10, width: 10, borderRadius: 5, backgroundColor: "#000", }, credentialText: { fontSize: 16, }, }); ``` This component displays all existing credentials that match the verification request, and provides a UI for the user to select the credential they wish to share. Identifiers of the selected credentials are assigned to the `selectedCredentialIds` variable, making them available for use in the next steps. 2. Add the following code under the `// Step 2.2: Import Credential selector component` comment in the `proximity-presentation.tsx` file to import the `RequestCredentialSelector` component created in the previous step: ```ts title="app/proximity-presentation.tsx" import RequestCredentialSelector from "@/components/RequestCredentialSelector"; ``` Before we can display and use the `RequestCredentialSelector` component, we need to implement a functionality that allows users to select which credentials they want to share. The `handleToggleSelection` function updates the `selectedCredentialIds` state array when users tap on credentials. This function is passed to the `RequestCredentialSelector` component to handle selection state, and the resulting array of selected credential identifiers will be used when sending the presentation response to the verifier. 3. Add the following code under the `// Step 2.3: Add handleToggleSelection function` comment to create the `handleToggleSelection` function: ```ts title="app/proximity-presentation.tsx" const handleToggleSelection = useCallback((id: string) => { setSelectedCredentialIds( (prev) => prev.includes(id) ? prev.filter((item) => item !== id) // Remove if already selected : [...prev, id], // Add if not selected ); }, []); ``` 4. Add the following code under the `{/* Step 2.4: Display request details */}` comment to display the `RequestCredentialSelector` component to the user, enabling them to select which credentials to share: ```ts title="app/proximity-presentation.tsx" ``` 5. Run the app, select the **Present Credential** button and the use your verifier testing device to scan the displayed QR code. You should see a result similar to the following: * As the user selects the **Present Credential** button a new proximity presentation session is created and a QR code is displayed. * When a verifier application scans the QR code, the devices will automatically exchange public keys to establish a secure communication channel, enabling the verifier to send a presentation request, which details: * What credentials are required. * What specific claims are required from these credentials. * The request details are then displayed by the `RequestCredentialSelector` component and enable the user to select which matching credential to share with the verifier. We will implement the logic to share the information with the verifier in the next step. ### Step 3: Send a response [#step-3-send-a-response] Tutorial Workflow The next (and final!) capability you need to build is for the wallet application to send a presentation response upon receiving consent from the user to share information with the verifier. Once the user provides this consent by selecting the **Send Response** button, the wallet application should call the SDK's `sendResponse` method to share the selected credentials with the verifier as a presentation response. 1. In the `ContentView` file replace the print `statement` under the `// Proximity Presentation - Step 3.1: Send a credential response` comment with the following code to call the [`sendResponse`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/proximitypresentationsession/sendresponse\(credentialids:terminatesession:\)) method when the user selects the **Send Response** button: ```swift title="ContentView" Task { do { let _ = try await proximityPresentationSession?.sendResponse(credentialIds: [id]) // set presentation session to nil after sending a response proximityPresentationSession = nil // Return to root view after the response is sent navigationPath = NavigationPath() } catch { print(error) } } ``` The [`sendResponse`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/proximitypresentationsession/sendresponse\(credentialids:terminatesession:\)) function signs the presentation response with the user’s device private key (to prove [Device authentication](/docs/concepts/mdocs/core-capabilities#device-authentication)) and shares it as an encoded CBOR file. 1. Open the `PresentationSelectCredentialsScreen.kt` file and add the following code under the `// Step 3.1: Create function to send the credential response` comment to create a new function that calls the SDK's [`sendResponse`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-proximity-presentation-session/send-response.html) method. ```kotlin title="PresentationSelectCredentialsScreen.kt" private suspend fun sendResponse(credentialId: String, activity: Activity) { MobileCredentialHolder.getInstance() .getCurrentProximityPresentationSession() ?.sendResponse(listOf(credentialId), activity) } ``` The [`sendResponse`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-proximity-presentation-session/send-response.html) function signs the presentation response with the user’s device private key (to prove [Device authentication](/docs/concepts/mdocs/core-capabilities#device-authentication)) and shares it as an encoded CBOR file. 2. Add the following code under the `// Step 3.2: Send response` to call the created `sendResponse` function when the user selects the "Send Response" button: ```kotlin title="PresentationSelectCredentialsScreen.kt" Button( onClick = { coroutineScope.launch { sendResponse(selectedCredentialId, activity) } }, Modifier.fillMaxWidth() ) { Text("Send Response") } ``` 1. Add the following code under the `// Step 3.1: Add handleSendResponse function` comment to create a new `handleSendResponse` function that calls the SDK's [`sendProximityPresentationResponse`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/sendProximityPresentationResponse.html) function: ```ts title="app/proximity-presentation.tsx" const handleSendResponse = useCallback(async () => { if (selectedCredentialIds.length === 0) { Alert.alert( "No Credentials Selected", "Please select at least one credential to send.", ); return; } try { const result = await sendProximityPresentationResponse({ credentialIds: selectedCredentialIds, }); if (result.isErr()) { await terminateSession(); throw new Error( `Error sending proximity response: ${result.error.message || result.error.type}`, ); } Alert.alert("Success", "Presentation response sent successfully!"); navigateToIndex(); } catch (err: unknown) { handleError(err instanceof Error ? err.message : String(err)); } }, [selectedCredentialIds, handleError, terminateSession, navigateToIndex]); ``` 2. Add the following code under the `{/* Step 3.2: Send response */}` comment to add a new button that calls the `handleSendResponse` function created in the previous step: ```ts title="app/proximity-presentation.tsx" Share credential ``` The [`sendProximityPresentationResponse`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/sendProximityPresentationResponse.html) function signs the presentation response with the user’s device private key (to prove [Device authentication](/docs/concepts/mdocs/core-capabilities#device-authentication)) and shares it as an encoded CBOR file. ### Step 4: Test the application [#step-4-test-the-application] 1. Run the app. 2. Select the **Present Credentials** button. 3. Use your testing verifier app to scan the presented QR code and send a presentation request. 4. Back on the holder device, select the matching credential to share and select the **Send Response** button. You should see a result similar to the following: As the user selects the credential to share, the verifier app will receive the presentation response, verify any incoming credentials and display the verification results. Congratulations, you have now completed this tutorial, and should have a working wallet application that can claim an mDoc using an OID4VCI workflow, and present it to a verifier via a proximity presentation workflow. ## Summary [#summary] You have just used the [mDocs Holder SDKs](/docs/holding/sdk-overview) to build an application that can present a claimed [mDoc](/docs/concepts/mdocs) via a proximity presentation workflow as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). Tutorial Workflow This was achieved by building the following capabilities into the application: * Generating a QR code for the verifier to scan and establish a secure communication channel. * Receive and handle a presentation request from the verifier. * Display matching credentials to the user and ask for consent to share them with the verifier. * Send matching credentials to the verifier as a presentation response. ## What's next? [#whats-next] * You can build additional capabilities into your new application: * Present a claimed mDoc for verification via an [online presentation workflow](/docs/holding/remote-presentation-tutorial) into your new application. * You can check out the SDKs reference documentation for more details on the available functions and classes: * [iOS](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk) * [Android](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/index.html) * [React Native](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html) # Remote presentation URL: /docs/holding/remote-presentation-overview Remote presentation allows holders to present a credential for verification without the need for a physical presence. This is particularly useful for scenarios where the verifier and holder are not in the same location, such as online transactions or remote identity verification processes. ## mDocs [#mdocs] The [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) specification establishes interoperable methods for remote presentation and verification of mDocs such as mobile driver’s licenses (**mDLs**) and other digital credentials. ### Verification requests [#verification-requests] The [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) specification defines two key aspects of remote verification requests: 1. **Wallet interaction**: Defines how the request and response are transferred between the verifier and the user's wallet: * HTTP Redirects: Standard HTTP redirects are used to pass information between the verifier and the wallet, typically via a web browser. * Digital Credentials API (DC API): A browser-integrated API for digital credential interactions. The DC API allows web apps to communicate securely and seamlessly with wallet apps, improving user experience by eliminating multiple browser redirects. 2. **Request type**: Defines how the verifier requests the credential from the user's device and how should the device respond: * Device retrieval: Based on the [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) standard and defined in ISO/IEC 18013-7 Annex A. The verifier directly requests a credential from the user's device, and the device retrieves and presents the necessary credential data in response. * [OID4VP (OpenID for Verifiable Presentations)](/docs/verification/oid4vp): Based on the [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) protocol and defined in ISO/IEC 18013-7 Annex B. The verifier sends an authentication request, and the device responds with a verifiable presentation containing the requested data. #### ISO/IEC 18013-7:2025 Annexes [#isoiec-18013-72025-annexes] ISO/IEC 18013-7:2025 defines specific annexes for each combination of wallet interaction and request type: | Annex | Request type | Transfer method | Platform support | | ----- | ---------------- | ----------------------- | ------------------------------------------------------- | | A | Device Retrieval | HTTPs | General purpose, browser-based interactions | | B | OID4VP | HTTPs Redirects | General purpose, browser-based interactions | | C | Device Retrieval | Digital Credentials API | Supported on **iOS** devices and the **Safari** browser | The next iteration of ISO/IEC 18013-7 is expected to define an additional Annex D: | Annex | Request type | Transfer method | Platform support | | ----- | ------------ | ----------------------- | ---------------------------------------------------------------- | | D | OID4VP | Digital Credentials API | Supported on **Android** devices and **Chromium-based** browsers | ### Presentation flows [#presentation-flows] The [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) specification defines different flows for requesting and presenting mDocs based on the type of verifier application and the responding device: * **Verifier web applications:** Typically use HTTP redirects or the Digital Credentials API to invoke the wallet from a desktop or mobile browser. Web applications support same-device and cross-device flows: * **Same-device flow:** You start the verification experience in a mobile browser and are redirected to a wallet app (where your mDoc is stored) installed on the same mobile device to respond to the request. For example, an online banking portal accessed from your mobile browser prompts you to open your wallet app on the same phone to present your mDoc for identity verification. * **Cross-device flow:** You start the verification experience in a desktop browser and use your mobile device (where your mDoc is stored in a wallet app) to respond to the verification request. For example, you visit a government tax website on your desktop computer, and scan a QR code with your mobile wallet app to present your mDoc. * **Verifier mobile applications:** Can use platform capabilities or app-to-app communication to initiate and complete the verification. Similar to web applications, mobile applications support both same-device and cross-device flows: * **Same-device flow:** You start the experience on a mobile app, and are redirected to a wallet app installed on the same device to present the credential. For example, a tax filing mobile app redirects you to your wallet app on the same phone to verify your identity with your mDoc before filing your return. * **Cross-device flow:** You start the experience on a **mobile app**, but use a **different mobile device** to present the credential. This may happen if your credential is only available on another device you own. For example, you use your tablet to access a government service app, but the app enables you to scan a QR code using your phone where the required mDoc is available in a wallet app. The exact protocols and flows used depend on the requesting application and the user's wallet. Different wallets and browsers support different combinations of these request types and transfer methods. # Learn how to build an application that can present an mDoc remotely URL: /docs/holding/remote-presentation-tutorial ## Introduction [#introduction] In this tutorial you will use the [mDocs Holder SDKs](/docs/holding/sdk-overview) to build an application that can present a claimed [mDoc](/docs/concepts/mdocs) to a verifier remotely via a [remote presentation workflow](/docs/verification/remote-overview) as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). This app will support both same-device and cross-device workflows to accommodate flexible user journeys. ### Same-device workflow [#same-device-workflow] Tutorial Workflow 1. The user interacts with a website on their mobile device browser. 2. The user is asked to present information as part of the interaction. 3. The user is redirected to the application you will build in this tutorial. 4. The application authenticates the user. 5. The user is informed of what information they are about to share and provide their consent. 6. The user is redirected back to the browser where verification results are displayed, enabling them to continue with the interaction. The result will look something like this: ### Cross-device workflow [#cross-device-workflow] Tutorial Workflow 1. The user interacts with a website on their desktop browser. 2. The user is asked to present information as part of the interaction. 3. The user scans a QR code using a mobile device where the tutorial application is installed. 4. The tutorial application is launched on the mobile device. 5. The tutorial application authenticates the user. 6. The user is informed of what information they are about to share and provide their consent. 7. Verification results are displayed in the user's desktop browser, enabling them to continue with the interaction. The result will look something like this: ## Prerequisites [#prerequisites] Before you get started, let's make sure you have everything you need. ### Prior knowledge [#prior-knowledge] * The presentation workflow described in this tutorial is based on [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). If you are unfamiliar with these technical specifications, refer to the following resources for more information: * What are [mDocs](/docs/concepts/mdocs)? * What is [credential verification](/docs/verification)? * Breakdown of the [remote presentation workflow](/docs/verification/remote-overview). * We assume you have experience developing applications in the relevant programming languages and frameworks (Swift for iOS, Kotlin for Android and TypeScript for React Native). If you need to get a holding solution up and running quickly with minimal development resources and in-house domain expertise, [talk to us](http://mattr.global/contact-us) about our white-label [MATTR GO Hold app](https://mattr.global/platforms/go) which might be a good fit for you. ### Prerequisite tutorial [#prerequisite-tutorial] * You must complete the [Claim a credential tutorial](/docs/holding/credential-claiming-tutorial) and claim the mDoc provided in the tutorial. * This application is used as the base for the current tutorial. ### Testing devices [#testing-devices] * Supported iOS device to run the built application on, setup with: * Biometric authentication (Face ID, Touch ID). * Available internet connection. * Supported Android device to run the built application on, setup with: * Biometric authentication (Face recognition, fingerprint recognition). * Available internet connection. * [Debugging](https://developer.android.com/studio/debug/dev-options#enable) enabled. * Supported iOS and/or Android device to run the built application on, setup with: * Available internet connection. * iOS: * Biometric authentication (Face ID, Touch ID). * Android: * Biometric authentication (Face recognition, fingerprint recognition). * [Debugging](https://developer.android.com/studio/debug/dev-options#enable) enabled. Got everything? Let's get going! ## Tutorial steps [#tutorial-steps] To enable a user to present a stored [mDoc](/docs/concepts/mdocs) to a verifier via an [online presentation workflow](/docs/verification/remote-overview), you will build the following capabilities into your application: 1. Register the verifier's Authorization endpoint. 2. Create an online presentation session. 3. Handle a presentation request. 4. Send a presentation response. ### Step 1: Register the verifier's Authorization endpoint [#step-1-register-the-verifiers-authorization-endpoint] The [Authorization endpoint](/docs/verification/remote-overview#authorization-request) is a URI associated with an application identifier in the MATTR VII tenant configuration. It is used to [invoke an application](/docs/verification/remote-web-verifiers/workflow#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application) that will handle the presentation request. The application then uses the URI to retrieve a request object, which details what information is required for verification. Online verifiers are recommended to generate this URI as a [Universal link](https://developer.apple.com/ios/universal-links/) for iOS and [App Link](https://developer.android.com/training/app-links#android-app-links) for Android, as this enables them to explicitly define and validate applications that can respond to their verification requests. However, for simplicity reasons in this tutorial our verifier is using a [custom URI scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) for iOS and a [deep link](https://developer.android.com/training/app-links#deep-links) for Android, both matching the default scheme defined by the OID4VP specification (`mdoc-openid4vp`). This means that you need to configure the application to be able to handle this custom URI scheme. 1. Open the Xcode project with the application built in the [Claim a credential](/docs/holding/credential-claiming-tutorial) tutorial. 2. Register `mdoc-openid4vp` as a recognized [URL scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app): * Open the project view and select your application target. * Select the *Info* tab. * Scroll down and expand the *URL Types* area. * Select the plus button. * Insert `mdoc-openid4vp` in both the *Identifier* and *URL Schemes* fields. Register links in iOS app 3. [Run](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device) the app and then close it (this updates the app on your testing device) and perform the following instructions: * Use a desktop browser to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). * Select **OID4VP (Redirect)** from the *Select Experience* list. * Select **Request credentials**. * Open the camera on your testing mobile device and scan the QR code. * Confirm opening the QR code with your tutorial application. * The tutorial application should be launched on your testing mobile device. 1. Open the Android Studio project with the application built in the [Claim a credential](/docs/holding/credential-claiming-tutorial) tutorial. 2. Open your `AndroidManifest.xml`. 3. Add the following [intent filter](https://developer.android.com/guide/components/intents-filters#Receiving) to your `MainActivity`: ```xml title="AndroidManifest.xml" ``` 4. [Run](https://developer.android.com/studio/run#basic-build-run) the app and then close it (this updates the app on your testing device) and perform the following instructions: * Use a desktop browser to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). * Select **OID4VP (Redirect)** from the *Select Experience* list. * Select **Request credentials**. * Open the camera on your testing mobile device and scan the QR code. * Confirm opening the QR code with your tutorial application. * The tutorial application should be launched on your testing mobile device. 1. Open the project with the application built in the [Claim a credential](/docs/holding/credential-claiming-tutorial) tutorial. 2. Open the `app.config.ts` file and replace the `scheme` property assignment under the `// Online presentation - Step 1.1: Update application custom scheme` comment with the following: ```ts title="app.config.ts" scheme: "mdoc-openid4vp", ``` 3. Delete the `ios` and `android` directories in the project's root. This is required to ensure the scheme changes are applied correctly. ### Step 2: Create an online presentation session [#step-2-create-an-online-presentation-session] Now that the application can handle an OID4VP custom URI scheme, the next step is to build the capability to use the request URI to retrieve the request object. This object details: * What credentials are required. * What specific claims are required from these credentials. * What MATTR VII tenant to interact with. 1. In your project's `ContentView` file, add the following code under the `// Online Presentation - Step 2.1: Create a variable to hold the online presentation session object` comment to create a variable that will hold the online presentation session: ```swift title="ContentView" var onlinePresentationSession: OnlinePresentationSession? ``` The following step is also included in the [Proximity presentation tutorial](/docs/holding/proximity-presentation-tutorial). If you had already completed this tutorial you may skip to step 3. 2. Add the following code under the `// Proximity and Online Presentation: Create variables for credential presentations` comment to create the following variables: ```swift title="ContentView" var matchedCredentials: [MobileCredential] = [] var matchedMetadata: [MobileCredentialMetadata] = [] var credentialRequest: [MobileCredentialRequest] = [] ``` * `matchedCredentials` : Holds stored credentials that match the credential request. * `matchedMetadata` : Holds metadata of credentials that match the credential request. * `credentialRequest`: Holds the credentials that were requested for verification. 3. Replace the `print` statement under the `// Online Presentation - Step 2.3: Create online presentation session` comment with the following code to create a function that calls the SDK's [`createOnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/createonlinepresentationsession\(authorisationrequesturi:requiretrustedverifier:\)) method with the `authorizationRequestURI` parameter (the request URI retrieved from the link/QR code): ```swift title="ContentView" Task { do { onlinePresentationSession = try await mobileCredentialHolder.createOnlinePresentationSession(authorizationRequestUri: authorizationRequestURI, requireTrustedVerifier: false) let matched = onlinePresentationSession?.getMatchedCredentials() ?? [] matchedMetadata = matched .flatMap { $0.matchedMobileCredentials } credentialRequest = matched .map { $0.request } } catch { print(error.localizedDescription) } } ``` This function: * Creates an `OnlinePresentationSession` instance and assigns it to the `onlinePresentationSession` variable. * Stores matched [`MobileCredentialMetadata`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialmetadata) in the `matchedMetadata` variable and the [`MobileCredentialRequest`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialrequest) in the `credentialRequest` variable in our `ViewModel` to enable displaying these values to the user. We chose to set `requireTrustedVerifier` parameter to `false` because we want the SDK to trust all verifiers by default. If you require to limit the verifiers a user can interact with, you may want to manually add trusted verifier certificates and set the parameter to `true`. You can learn more about certificate management in our [SDK docs.](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/addtrustedverifiercertificates\(certificates:\)) 4. Add the following code under the `// Online Presentation - Step 2.4: Create session from request URI` comment to add an [onOpenURL modifier](https://developer.apple.com/documentation/swiftui/view/onopenurl\(perform:\)) that will call the `createOnlinePresentationSession` function when the application is launched following selecting a link (same-device flow) or scanning a QR code (cross-device flow) that includes a registered URI: ```swift title="ContentView" .onOpenURL { url in Task { await viewModel.createOnlinePresentationSession(authorizationRequestURI: url.absoluteString) } // Navigate to online presentation view viewModel.navigationPath.append(NavigationState.onlinePresentation) } ``` Now, once a user opens an online presentation link on their device, an online presentation session will be created the user will be navigated to a new view, which you will implement in the next step. 1. Create a new file named `OnlinePresentationScreen.kt` and add the following code to the file: ```kotlin title="OnlinePresentationScreen.kt" package com.example.holdertutorial import android.app.Activity import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import global.mattr.mobilecredential.holder.deviceretrieval.devicerequest.DataElements import global.mattr.mobilecredential.holder.deviceretrieval.deviceresponse.NameSpace import global.mattr.mobilecredential.holder.dto.MobileCredential import global.mattr.mobilecredential.holder.dto.MobileCredentialMetaData import global.mattr.mobilecredential.holder.MobileCredentialHolder import global.mattr.mobilecredential.holder.onlinepresentation.OnlinePresentationSession import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @Composable fun OnlinePresentationScreen(activity: Activity, requestUri: String) { var session: OnlinePresentationSession? by remember { mutableStateOf(null) } // Step 2.1: Create an online presentation session val (requested, matched) = session?.getMatchedCredentials()?.entries?.firstOrNull() ?: return var matchedCredentials by remember { mutableStateOf(matched) } var selectedCredentialId by remember { mutableStateOf(matchedCredentials.first().id) } val coroutineScope = rememberCoroutineScope() Column(Modifier.verticalScroll(rememberScrollState())) { Text("REQUESTED DATA", style = MaterialTheme.typography.titleLarge) Card(Modifier.padding(vertical = 8.dp)) { Document(requested.docType, requested.namespaces.value.toUi()) } Spacer(Modifier.padding(12.dp)) Text("MATCHED CREDENTIALS", style = MaterialTheme.typography.titleLarge) Spacer(Modifier.padding(6.dp)) matchedCredentials.forEach { matchedCredential -> // Step 3.2: Display matching credentials and claims } // Step 4.1: Send response } } // Step 3.1: Create function to add values to claims private fun Map.toUi() = mapValues { (_, dataElements) -> dataElements.value.keys.toSet() } ``` This code is very similar to the one used in the in the `PresentationSelectCredentials.kt` file in the [Proximity Presentation](/docs/holding/proximity-presentation-tutorial) tutorial, to avoid creating dependencies between the tutorials. In your own project you can use the same components for both presentation workflows. 2. Add the following code under the `// Step 2.1: Create an online presentation session` comment to create a new online presentation session when the `OnlinePresentationScreen` composable appears on the screen: ```kotlin title="OnlinePresentationScreen.kt" LaunchedEffect(requestUri) { withContext(Dispatchers.IO) { val mdocHolder = MobileCredentialHolder.getInstance() while (!mdocHolder.initialized) delay(100) session = mdocHolder .createOnlinePresentationSession(requestUri, requireTrustedVerifier = false) } } ``` This calls the SDK's [createOnlinePresentationSession](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/create-online-presentation-session.html) function, passing `requestUri` as an argument, which is the Authorization request URI retrieved from the deep link/QR code. The function returns an [OnlinePresentationSession](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.onlinepresentation/-online-presentation-session/index.html) object which is stored in the declared `session` variable. This object provides a `getMatchedCredentials()` method, which details the requested information ([MobileCredentialRequest](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.dto/-mobile-credential-request/index.html)) and any existing credentials that match it ([MobileCredentialMetaData](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.dto/-mobile-credential-meta-data/index.html)). We will use this information in the next steps to display this information to the user. We must wait for the SDK instance to be initialized, because in this tutorial the SDK initialization is called in a coroutine in the `MainActivity.onCreate` method. We chose to set `requireTrustedVerifier` parameter to `false` because we want the SDK to trust all verifiers by default. If you require to interact with a limited list of verifiers, you may want to manually add trusted verifier certificates and set the parameter to `true`. You can learn more about certificate management in our [SDK docs](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/add-trusted-verifier-certificates.html). 3. In your `MainActivity.kt` file, add the following code under the `// Online Presentation - Step 2.2: Add "Online Presentation" screen call` comment to connect the created `OnlinePresentationScreen` composable to the navigation graph: ```kotlin title="MainActivity.kt" composable( "onlinePresentation", deepLinks = listOf( navDeepLink { uriPattern = "mdoc-openid4vp://{wildcard}" } ) ) { @Suppress("DEPRECATION") val deepLink = it.arguments ?.getParcelable(NavController.KEY_DEEP_LINK_INTENT) ?.data ?.toString() ?: "" OnlinePresentationScreen(this@MainActivity, deepLink) } ``` Previously you [added an intent filter](/docs/holding/remote-presentation-tutorial#register-the-verifiers-authorization-endpoint) for [deep links](https://developer.android.com/training/app-links#deep-links) with a `mdoc-openid4vp` scheme, so that the app is started when the intent with the deep link is filtered. Now, when the app is opened via this deep link, it will also start the `OnlinePresentationScreen` composable, and pass the deep link as the `requestUri` argument. 1. In your project's `app` directory, create a new file named `online-presentation.tsx` and add the following scaffolding code: ```ts title="app/online-presentation.tsx" // Step 3.2: Import Credential selector component import { useHolder } from "@/providers/HolderProvider"; import { type OnlinePresentationSession, createOnlinePresentationSession, } from "@mattrglobal/mobile-credential-holder-react-native"; import { useGlobalSearchParams, useRouter } from "expo-router"; import React, { useState, useEffect, useCallback } from "react"; import { Alert, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; export default function OnlinePresentation() { const router = useRouter(); const { scannedValue: authorizationRequestUri } = useGlobalSearchParams<{ scannedValue: string; }>(); const { isHolderInitialized } = useHolder(); const [onlinePresentationSession, setOnlinePresentationSession] = useState(null); const [error, setError] = useState(null); const [requests, setRequests] = useState< OnlinePresentationSession["matchedCredentials"] >([]); const [selectedCredentialIds, setSelectedCredentialIds] = useState< string[] >([]); const handleError = useCallback((message: string) => { setError(message); console.error(message); }, []); // Step 3.3: Add handleToggleSelection function // Step 2.6: Create Online Presentation Session // Step 4.1: Add handleSendResponse function if (error) { return ( Error: {error} ); } if (!onlinePresentationSession) { return ( No online presentation session ); } // Step 3.4: Display presentation session details } const styles = StyleSheet.create({ container: { flex: 1, padding: 16, }, errorText: { color: "red", fontSize: 16, }, button: { backgroundColor: "#007AFF", paddingVertical: 12, paddingHorizontal: 20, borderRadius: 8, alignItems: "center", marginTop: 20, }, buttonText: { color: "white", fontSize: 16, fontWeight: "600", }, verifierSection: { backgroundColor: "#f0f0f0", padding: 10, borderRadius: 8, marginBottom: 15, }, label: { fontWeight: "bold", fontSize: 14, textTransform: "uppercase", marginBottom: 5, }, verifierText: { fontSize: 16, }, }); ``` 2. Open the `app/index.tsx` file and add the following code inside the `handleScanComplete` function under the `// Online Presentation - Step 2.2: Handle the 'mdoc-openid4vp://' scheme prefix` comment: ```ts title="app/index.tsx" else if (scannedValue.startsWith('mdoc-openid4vp://')) { router.replace({ pathname: '/online-presentation', params: { scannedValue } }) } ``` The application will now redirect to the `online-presentation` screen whenever a QR code which includes a link prefixed with `mdoc-openid4vp://` is scanned. This handles invoking the correct screen in cross-device workflows. Now, we need to make sure the application navigates to the `online-presentation` screen when following deep links that are prefixed with `mdoc-openid4vp://` as part of same-device workflows. Add the following code to the `HolderProvider` component to handle linking: 3. Open the `app/providers/HolderProvider.tsx` and add the following code under the `// Online Presentation - Step 2.3: Import expo-linking and expo-router` to import the required redirect components: ```ts title="app/providers/HolderProvider.tsx" import * as Linking from "expo-linking"; import { useRouter } from "expo-router"; ``` 4. Add the following code under the `// Online Presentation - Step 2.4: Initialize router variable` comment to use the imported `useRouter` component: ```ts title="app/providers/HolderProvider.tsx" const router = useRouter(); ``` 5. Add the following code under the `// Online Presentation - Step 2.5: Handle deep link` comment to use the `router` component redirect the user to the `online-presentation` screen when following a deep link prefixed with `mdoc-openid4vp://`: ```ts title="app/providers/HolderProvider.tsx" useEffect(() => { if (!isHolderInitialized) return; const handleDeepLink = (event: { url: string }) => { const { url } = event; console.log("Deep link received:", url); if (url.startsWith("mdoc-openid4vp://")) { router.replace({ pathname: "/online-presentation", params: { scannedValue: url }, }); } }; Linking.getInitialURL().then((url) => { if (url) { console.log("Initial URL:", url); handleDeepLink({ url }); } }); const subscription = Linking.addEventListener("url", handleDeepLink); return () => subscription.remove(); }, [isHolderInitialized, router]); ``` This function was created in the [Claim a credential tutorial](/docs/holding/credential-claiming-tutorial) to handle QR codes which include OID4VCI credential offers. 6. Return to the `online-presentations.tsx` file and add the following code under the `// Step 2.6: Create Online Presentation Session` comment to create a function that calls the SDK's [`createOnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/createOnlinePresentationSession.html) function with the `authorizationRequestURI` parameter (the request URI retrieved from the deep link/QR code) to create an `OnlinePresentationSession` instance and assign it to the `session` variable: ```ts title="app/online-presentation.tsx" useEffect(() => { if (!isHolderInitialized || !authorizationRequestUri) return; const createSession = async () => { try { const result = await createOnlinePresentationSession({ authorizationRequestUri, requireTrustedVerifier: false, }); if (result.isErr()) { throw new Error( result.error.message || "Error creating presentation session", ); } setOnlinePresentationSession(result.value); if (result.value.matchedCredentials) { setRequests(result.value.matchedCredentials); } } catch (err: unknown) { handleError(err instanceof Error ? err.message : String(err)); } }; createSession(); }, [isHolderInitialized, authorizationRequestUri, handleError]); ``` Once the `result` (the response returned by the [`createOnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/createOnlinePresentationSession.html) function) is available, your application can access its `matchedCredentials` object, which contains the list of credentials that match the verifier's request. Your application will use this information to display the request details and allow the user to select which credentials to share. We chose to set `requireTrustedVerifier` parameter to `false` because we want the SDK to trust all verifiers by default. If you require to interact with a limited list of verifiers, you may want to manually add trusted verifier certificates and set this parameter to `true`. You can learn more about certificate management in our [SDK docs.](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/addTrustedVerifierCertificates.html) 7. Open the `app/index.tsx` file and add the following code under the `{/* Online Presentation - Step 2.7: Add Online Presentation button */}` comment to add a button that will open the scanner and enable the user to scan a QR code and start an online presentation session: ```ts title="app/index.tsx" setIsScannerVisible(true)} > Online Presentation ``` 8. Run the app for your targeted device (using `yarn android --device` and/or `yarn ios --device` to rebuild the deleted folders) and perform the following instructions: * Use a desktop browser to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). * Select **OID4VP (Redirect)** from the *Select Experience* list. * Select **Request credentials**. * Open the camera on your testing mobile device and scan the QR code. * Confirm opening the QR code with your tutorial application. * The tutorial application should be launched on your testing mobile device. You have not yet implemented the logic to handle the presentation request, so the application will not display any information at this stage. Let's proceed to the next step to fix that. ### Step 3: Handle a presentation request [#step-3-handle-a-presentation-request] We will now build the capability to use information retrieved by the `createOnlinePresentationSession` function to handle the presentation request. This includes: * Displaying what information is requested. * Displaying what existing credentials match the requested information. * Displaying what information from these existing claims will be shared with the verifier. * Asking for the user's consent to share requested information from matching credentials. The following two steps are also included in the [Proximity presentation tutorial](/docs/holding/proximity-presentation-tutorial). If you had already completed this tutorial you may skip to step 3. 1. Replace the `print` statement under the `// Proximity and Online Presentation: Retrieve a credential from storage` comment with the following code create a function that uses the SDK's [getCredential](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/getcredential\(credentialid:fetchupdatedstatuslist:\)) method to retrieve a credential from the application storage: ```swift title="ContentView" Task { do { let credential = try await mobileCredentialHolder.getCredential(credentialId: id) matchedCredentials.append(credential) } catch { print(error) } } ``` The [MobileCredentialMetadata](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialmetadata) object does not include the values of claims included in the credential. To display these values, the above function calls the SDK's [getCredential](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/getcredential\(credentialid:fetchupdatedstatuslist:\)) method with the `id` property of the [MobileCredentialMetadata](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialmetadata). 2. Create a new file called `PresentCredentialsView.swift` and paste the following code to create a view to display credential requests and matching credentials stored in the application: ```swift title="PresentCredentialsView" import MobileCredentialHolderSDK import SwiftUI struct PresentCredentialsView: View { var viewModel: PresentCredentialsViewModel @State var selectedID: String? var body: some View { ScrollView { VStack(alignment: .leading, spacing: 20) { Text("Requested Documents") .font(.headline) .padding(.leading) ForEach(viewModel.requestedDocuments, id: \.docType) { requestedDocument in DocumentView(viewModel: DocumentViewModel(from: requestedDocument)) } Text("Matched Credentials") .font(.headline) .padding(.leading) ForEach(viewModel.matchedMetadata, id: \.id) { matchedMetadata in VStack(alignment: .leading, spacing: 10) { if let matchedCredential = viewModel.matchedMobileCredential(id: matchedMetadata.id) { DocumentView(viewModel: DocumentViewModel(from: matchedCredential)) .padding(.vertical) .background(selectedID == matchedMetadata.id ? Color.blue.opacity(0.2) : Color.clear) .onTapGesture { guard selectedID != matchedMetadata.id else { selectedID = nil return } selectedID = matchedMetadata.id } Button("Hide claim values") { viewModel.matchedCredentials.removeAll(where: { $0.id == matchedMetadata.id }) } .frame(maxWidth: .infinity, alignment: .center) } else { DocumentView(viewModel: DocumentViewModel(from: matchedMetadata)) .padding(.vertical) .background(selectedID == matchedMetadata.id ? Color.blue.opacity(0.2) : Color.clear) .onTapGesture { guard selectedID != matchedMetadata.id else { selectedID = nil return } selectedID = matchedMetadata.id } Button("Show claim values") { viewModel.getCredentialAction(matchedMetadata.id) } .frame(maxWidth: .infinity, alignment: .center) } } } } } if selectedID != nil { Button("Send Response") { viewModel.sendCredentialAction(selectedID!) } .buttonStyle(.borderedProminent) .clipShape(Capsule()) .frame(maxWidth: .infinity, alignment: .center) } } } // MARK: PresentCredentialsViewModel class PresentCredentialsViewModel { @Binding var requestedDocuments: [MobileCredentialRequest] @Binding var matchedCredentials: [MobileCredential] @Binding var matchedMetadata: [MobileCredentialMetadata] var getCredentialAction: (String) -> Void var sendCredentialAction: (String) -> Void init( requestedDocuments: Binding<[MobileCredentialRequest]>, matchedCredentials: Binding<[MobileCredential]>, matchedMetadata: Binding<[MobileCredentialMetadata]>, sendCredentialAction: @escaping (String) -> Void, getCredentialAction: @escaping (String) -> Void ) { self._requestedDocuments = requestedDocuments self._matchedCredentials = matchedCredentials self._matchedMetadata = matchedMetadata self.sendCredentialAction = sendCredentialAction self.getCredentialAction = getCredentialAction } func matchedMobileCredential(id: String) -> MobileCredential? { matchedCredentials.first(where: { $0.id == id }) } } ``` The `PresentCredentialsView` view is used to: * Display requested information. * Display stored credentials that include the requested information. * Enable the user to provide consent to sharing the requested information with the verifier. The `PresentCredentialsViewModel` object is used to reference values from a credential request. It takes two closures in its initializer: * `getCredentialAction: (String) -> Void` is used to display claim values. * `sendCredentialAction: (String) -> Void` is used to send a credential response to the verifier once the user selected a credential and provided consent by selecting the **Send Response** button. 3. Back in your `ContentView` file, Replace `EmptyView` under the `// Online Presentation - Step 3.3: Display online presentation view` comment with the new view that you created: ```swift title="ContentView" PresentCredentialsView( viewModel: PresentCredentialsViewModel( requestedDocuments: $viewModel.credentialRequest, matchedCredentials: $viewModel.matchedCredentials, matchedMetadata: $viewModel.matchedMetadata, sendCredentialAction: viewModel.sendOnlinePresentationSessionResponse(id:), getCredentialAction: viewModel.getCredential(id:) ) ) ``` 4. Replace the `return false` statement under the `// Online Presentation - Step 3.4: View Online Presentation` comment with the following code to enable the user to manually navigate to the presentation session view if required: ```swift title="ContentView" onlinePresentationSession != nil ``` 5. [Run](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device) the app and then close it (this updates the app on your testing device) and perform the following instructions: * Use a desktop browser to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). * Select **OID4VP (Redirect)** from the *Select Experience* list. * Select **Request credentials**. * Open the camera on your testing mobile device and scan the QR code. * Confirm opening the QR code with your tutorial application. * The tutorial application should be launched on your testing mobile device, displaying the verification request and any matching credentials. * When a credential is selected, it will be highlighted and a **Send Response** button will appear (the logic associated with the button will be implemented in the next step). The result will look something like this: 1. In the `OnlinePresentationScreen.kt` file, add the following code under the `// Step 3.1: Create function to add values to claims` comment to create a new function that will display the values of the claims the user is about to share: ```kotlin title="OnlinePresentationScreen.kt" private fun List.withClaimValues( from: MobileCredential ): List = map { credential -> if (credential.id != from.id) { credential } else { credential.copy( claims = credential.claims.mapValues { (namespace, claims) -> claims.map { claim -> val claimValue = from.claims[namespace]?.get(claim) claimValue?.let { "$claim: ${it.toUiString()}" } ?: claim }.toSet() } ) } } ``` This function retrieves all matching credentials from the [MobileCredentialMetaData](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.dto/-mobile-credential-meta-data/index.html) object and retrieves their matching values from the internal storage according to the credential's `id`. 2. Add the following code under the `// Step 3.2: Display matching credentials and claims` comment to display to the user what credentials and claims they are about to share with the verifier, as well as a button that enables the user to display the value of these claims: ```kotlin title="OnlinePresentationScreen.kt" val borderWidth = if (matchedCredential.id == selectedCredentialId) 4.dp else 0.dp Column( Modifier .clickable { selectedCredentialId = matchedCredential.id } .border(borderWidth, Color.Blue, RoundedCornerShape(16.dp)) .padding(8.dp) ) { Card(Modifier.fillMaxWidth()) { Document(matchedCredential.docType, matchedCredential.claims) } Button( onClick = { val credentialWithValues = MobileCredentialHolder.getInstance() .getCredential(matchedCredential.id, fetchUpdatedStatusList = false) matchedCredentials = matchedCredentials.withClaimValues(from = credentialWithValues) }, Modifier.fillMaxWidth() ) { Text("Show Values") } } Spacer(Modifier.padding(12.dp)) ``` 3. [Run](https://developer.android.com/studio/run#basic-build-run) the app and then close it (this updates the app on your testing device) and perform the following instructions: * Use a desktop browser to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). * Select **OID4VP (Redirect)** from the *Select Experience* list. * Select **Request credentials**. * Open the camera on your testing mobile device and scan the QR code. * Confirm opening the QR code with your tutorial application. * The tutorial application should be launched on your testing mobile device, displaying the verification request and any matching credentials. The result will look something like this: First you will create a component that will display the requested information and allow the user to select which credentials to share. The following step is also included in the [Proximity presentation tutorial](/docs/holding/proximity-presentation-tutorial#handle-a-presentation-request). If you had already completed this tutorial and created the `RequestCredentialSelector.tsx` component you may skip to step 3.2. 1. In the `app/components` directory, create a new file named `RequestCredentialSelector.tsx` and add the following code: ```ts title="app/components/RequestCredentialSelector.tsx" import type { MobileCredentialMetadata, PresentationSessionSuccessRequest, } from "@mattrglobal/mobile-credential-holder-react-native"; import type React from "react"; import { FlatList, type ListRenderItem, StyleSheet, Text, TouchableOpacity, View, } from "react-native"; type RequestCredentialSelectorProps = { requests: PresentationSessionSuccessRequest["request"]; selectedCredentialIds: string[]; onToggleSelection: (credentialId: string) => void; }; type RequestItem = PresentationSessionSuccessRequest["request"][number]; /** * Component that renders a list of credential requests and their matched credentials. * * @param props - The component props. * @param props.requests - The list of credential requests. * @param props.selectedCredentialIds - The list of selected credential IDs. * @param props.onToggleSelection - Callback function to toggle the selection of a credential. * @returns The rendered component. */ export default function RequestCredentialSelector({ requests, selectedCredentialIds, onToggleSelection, }: RequestCredentialSelectorProps) { const renderCredential: ListRenderItem = ({ item: cred, }) => { const isSelected = selectedCredentialIds.includes(cred.id); return ( onToggleSelection(cred.id)} > {isSelected && } {cred.branding?.name ?? "Credential"} ({cred.id}) ); }; const renderRequest: ListRenderItem = ({ item }) => ( Request Details {typeof item.request === "object" ? JSON.stringify(item.request, null, 2) : item.request} Matched Credentials: cred.id} renderItem={renderCredential} style={styles.credentialsList} contentContainerStyle={styles.credentialsListContent} /> ); return ( idx.toString()} renderItem={renderRequest} style={styles.requestsList} contentContainerStyle={styles.requestsListContent} /> ); } const styles = StyleSheet.create({ requestsList: { flex: 1, }, requestsListContent: { paddingBottom: 10, }, requestContainer: { backgroundColor: "#f0f0f0", padding: 10, borderRadius: 8, marginBottom: 15, }, requestInfo: { fontStyle: "italic", fontSize: 12, marginBottom: 10, }, label: { fontWeight: "bold", fontSize: 14, textTransform: "uppercase", marginBottom: 5, }, credentialsList: { maxHeight: 200, }, credentialsListContent: { paddingBottom: 10, }, credentialItem: { flexDirection: "row", alignItems: "center", marginBottom: 8, paddingVertical: 4, }, selectionIndicator: { height: 20, width: 20, borderRadius: 10, borderWidth: 1, borderColor: "#000", alignItems: "center", justifyContent: "center", marginRight: 8, }, selectionInner: { height: 10, width: 10, borderRadius: 5, backgroundColor: "#000", }, credentialText: { fontSize: 16, }, }); ``` This component displays all existing credentials that match the verification request, and provides a UI for the user to select the credential they wish to share. Identifiers of the selected credentials are assigned to the `selectedCredentialIds` variable, making them available for use in the next steps. 2. Open the `online-presentation.tsx` file and add the following code under the `// Step 3.2: Import Credential selector component` comment to import the `RequestCredentialSelector` component created in the previous step: ```ts title="app/online-presentation.tsx" import RequestCredentialSelector from "@/components/RequestCredentialSelector"; ``` Before we can display and use the `RequestCredentialSelector` component, we need to implement a functionality that allows users to select which credentials they want to share. The `handleToggleSelection` function updates the `selectedCredentialIds` state array when users tap on credentials. This function is passed to the `RequestCredentialSelector` component to handle selection state, and the resulting array of selected credential identifiers will be used when sending the presentation response to the verifier. 3. Add the following code under the `// Step 3.3: Add handleToggleSelection function` comment to create the `handleToggleSelection` function: ```ts title="app/online-presentation.tsx" const handleToggleSelection = useCallback((id: string) => { setSelectedCredentialIds( (prev) => prev.includes(id) ? prev.filter((item) => item !== id) // Remove if already selected : [...prev, id], // Add if not selected ); }, []); ``` 4. Add the following code under the `// Step 3.4: Display presentation session details` comment to display the request details and the `RequestCredentialSelector` component, passing in the required props: ```ts title="app/online-presentation.tsx" return ( {/* Display verifier information */} Verifier: {onlinePresentationSession.verifiedBy.value} {/* Component to select credentials for the presentation */} {/* Step 4.2: Add Send response button */} ); ``` 5. Run the app and perform the following instructions: * Use a desktop browser to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). * Select **OID4VP (Redirect)** from the *Select Experience* list. * Select **Request credentials**. * Open the camera on your testing mobile device and scan the QR code. * Confirm opening the QR code with your tutorial application. * The tutorial application should be launched on your testing mobile device. * The tutorial application should display the verification request and any matching credentials, enabling the user to select any specific credential for sharing. The result will look something like this: ### Step 4: Send response [#step-4-send-response] After displaying matching credentials to the user and enabling them to select what credential to share, the last thing you need to do is build the capability to share the selected credential with the verifier. 1. Replace the `print` statement under the `// Online Presentation - Step 4.1: Send online presentation response` comment with the following code to call the `sendResponse` method when the user selects the **Send Response** button: ```swift title="ContentView" Task { do { _ = try await onlinePresentationSession?.sendResponse(credentialIds: [id]) // set presentation session to nil after sending a response onlinePresentationSession = nil // Return to root view after the response is sent navigationPath = NavigationPath() } catch { print(error) } } ``` 1. Add the following code under the `// Step 4.1: Send response` comment to add a `Send Response` button, that will call the SDK's [sendResponse](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.onlinepresentation/-online-presentation-session/send-response.html) function and send the selected credential to the Verifier, when pressed: ```kotlin title="OnlinePresentationScreen.kt" Button( onClick = { coroutineScope.launch { session?.sendResponse(listOf(selectedCredentialId), activity) } }, Modifier.fillMaxWidth() ) { Text("Send Response") } ``` 1. Add the following code under the `// Step 4.1: Add handleSendResponse function` comment to create a function that calls the `sendResponse` method of the SDK's `onlinePresentationSession` object and sends the selected credential to the verifier: ```ts title="app/online-presentation.tsx" const handleSendResponse = useCallback(async () => { if (!onlinePresentationSession) return; if (selectedCredentialIds.length === 0) { Alert.alert( "No Credential Selected", "Please select at least one credential first.", ); return; } try { const sendResponseResult = await onlinePresentationSession.sendResponse({ credentialIds: selectedCredentialIds, }); if (sendResponseResult.isErr()) { throw new Error( sendResponseResult.error.message || "Failed to send presentation response", ); } router.replace("/"); Alert.alert("Success", "Presentation response sent successfully!"); } catch (err: unknown) { const message = err instanceof Error ? err.message : String(err); handleError(message); Alert.alert( "Error", "Failed to send presentation response. Terminating session...", ); await onlinePresentationSession.terminateSession(); } }, [onlinePresentationSession, selectedCredentialIds, router, handleError]); ``` 2. Add the following code under the `{/* Step 4.2: Add Send response button */}` comment to create a button that will enable the user to send a response to the verifier after selecting which credentials to share: ```ts title="app/online-presentation.tsx" Send Response ``` ### Step 5: Test the application [#step-5-test-the-application] Let's test that the application is working as expected in both workflows. #### Same-device workflow [#same-device-workflow-1] 1. Run the app and then close it (this updates the app on your testing device). 2. Use a browser on your testing mobile device to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/mdoc-online-presentation). 3. Select the **OID4VP (Redirect)** option from the *Select Experience* list. 4. Select **Request credentials**. 5. Select **Allow** to open the tutorial application. 6. The tutorial application should be launched on your testing mobile device. 7. Select the credential you wish to send to the verifier from the list of matched credentials. 8. Select **Send Response**. 9. You should be redirected back to the MATTR Labs remote presentation testing tool, where you will see a successful verification indication. The result will look something like this: #### Cross-device workflow [#cross-device-workflow-1] 1. Run the app and then close it (this updates the app on your testing device). 2. Use a desktop browser to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). 3. Select **OID4VP (Redirect)** from the *Select Experience* list. 4. Select **Request credentials**. 5. Open the camera on your testing mobile device and scan the QR code. 6. Confirm opening the QR code with your tutorial application. 7. The tutorial application should be launched on your testing mobile device. 8. Select the credential you wish to send to the verifier from the list of matched credentials. 9. Select **Send Response**. 10. Back on your desktop browser, you should see a successful verification indication. The result will look something like this: ## Summary [#summary] You have just used the [mDocs Holder SDKs](/docs/holding/sdk-overview) to build an application that can present a claimed [mDoc](/docs/concepts/mdocs) to a verifier remotely via an [online presentation workflow](/docs/verification/remote-overview) as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). Tutorial Workflow This was achieved by building the following capabilities into the application: 1. Handle an OID4VP [request URI](/docs/verification/remote-overview#authorization-request). 2. Create an online presentation session. 3. Handle a presentation request. 4. Send a presentation response. ## What's next? [#whats-next] * You can build additional capabilities into your new application: * Present a claimed mDoc for verification via a [proximity presentation workflow](/docs/holding/proximity-presentation-tutorial). * You can [build a web application](/docs/verification/remote-web-verifiers/tutorial) that will interact with your wallet application via an online verification workflow. * You can check out the SDKs reference documentation for more details on the available functions and classes: * [iOS](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk) * [Android](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/index.html) * [React Native](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html) # Getting started with the Holder SDKs URL: /docs/holding/sdk-getting-started Description: Set up access to the MATTR Pi mDocs Holder SDKs, configure SDK tethering, and initialize the SDK in your mobile application. This guide walks you through the steps required to start building with the MATTR Pi mDocs Holder SDKs. By the end, your mobile application will be connected to a MATTR VII tenant and ready to use SDK capabilities such as credential claiming and presentation. ### Request SDK access [#request-sdk-access] To access the MATTR Pi mDocs Holder SDKs, complete the [Get Started form](/docs/resources/get-started) with the following details: * Your organization name and contact information. * The platform(s) you plan to build for (iOS, Android, or React Native). * A brief description of your use case. Once your request is reviewed, you will receive access to the relevant SDK packages and the MATTR Portal. ### Create a MATTR VII tenant [#create-a-mattr-vii-tenant] Your application requires a MATTR VII tenant that serves as the backend for SDK operations including tethering, credential issuance, and verification. 1. Log into the [MATTR Portal](/docs/platform-management/portal). 2. [Create a new tenant](/docs/platform-management/portal#creating-a-tenant) to serve as the backend for your application. 3. Note the tenant URL (e.g., `https://your-tenant.vii.mattr.global`) — you will need it when initializing the SDK. ### Create a Holder Application [#create-a-holder-application] SDK Tethering is optional, but we recommend configuring it so your application can use capabilities such as Wallet Attestation and so you can view registered app instances from your tenant. To enable it, create a Holder Application on your MATTR VII tenant for each platform target. For more details on SDK tethering and the capabilities it enables, see [SDK Tethering](/docs/holding/sdk-operations/sdk-tethering). SDK Tethering is optional. To enable it, create a Holder Application on your MATTR VII tenant for each platform target (iOS and Android). This is a one-time setup process that registers your app with the tenant and allows app instances to obtain the necessary tokens for authentication and operation. Make a request of the following structure to create an iOS Holder Application configuration on your MATTR VII tenant: ```http title="Request" POST /v1/holder/applications ``` ```json title="Request body" { "name": "My iOS Holder Application", "clientId": "my-wallet-client", "type": "ios", "bundleId": "com.yourcompany.holderapp", "teamId": "YOUR_APPLE_TEAM_ID", "maxTimeOfflineInSecs": 864000, "appAttest": { "required": true, "environment": "production" } } ``` * `name`: A unique name to identify this Holder Application. * `clientId`: OAuth 2.0 `client_id` value that the holder application uses when requesting client attestations. This value is included as the `sub` claim in attestation JWTs and must match the `client_id` configured by issuers who trust this Holder Application. * `type`: Must be `ios`. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID. * `maxTimeOfflineInSecs`: Maximum number of seconds the SDK can operate offline before requiring a new license token. Must be between 1 day (86400) and 30 days (2592000). Defaults to 7 days (604800). * `appAttest`: App Attest configuration for the iOS holder application: * `required`: When `true`, the app instance must provide a valid App Attest attestation during registration and token renewal. When `false`, the app can fall back to assertion-only authentication. See [Attestation vs Assertion](/docs/holding/sdk-operations/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `environment`: The App Attest environment (`development` or `production`). Apple recommends using `development` for testing and `production` for distribution builds. A successful response returns a `201` status code with the created Holder Application: ```json title="Response" { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", // [!code focus] "name": "My iOS Holder Application", "clientId": "my-wallet-client", "type": "ios", "bundleId": "com.yourcompany.holderapp", "teamId": "YOUR_APPLE_TEAM_ID", "maxTimeOfflineInSecs": 864000, "appAttest": { "required": true, "environment": "production" } } ``` * `id`: A unique identifier for the Holder Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. Make a request of the following structure to create an Android Holder Application configuration on your MATTR VII tenant: ```http title="Request" POST /v1/holder/applications ``` ```json title="Request body" { "name": "My Android Holder Application", "clientId": "my-wallet-client", "type": "android", "packageName": "com.yourcompany.holderapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "maxTimeOfflineInSecs": 864000, "keyAttestation": { "required": true } } ``` * `name`: A unique name to identify this Holder Application. * `clientId`: OAuth 2.0 `client_id` value that the holder application uses when requesting client attestations. This value is included as the `sub` claim in attestation JWTs and must match the `client_id` configured by Issuers who trust this Holder Application. * `type`: Must be `android`. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/holding/android-app-signing) for more information. * `maxTimeOfflineInSecs`: Maximum number of seconds the SDK can operate offline before requiring a new license token. Must be between 1 day (86400) and 30 days (2592000). Defaults to 7 days (604800). * `keyAttestation`: Key Attestation configuration for the Android holder application: * `required`: When `true`, the app instance must provide a valid Key Attestation during registration and token renewal. When `false`, the app can register and renew tokens using just an authentication assertion. See [Attestation vs Assertion](/docs/holding/sdk-operations/sdk-tethering#attestation-vs-assertion-fall-back) for more details. A successful response returns a `201` status code with the created Holder Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", // [!code focus] "name": "My Android Holder Application", "clientId": "my-wallet-client", "type": "android", "packageName": "com.yourcompany.holderapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "maxTimeOfflineInSecs": 864000, "keyAttestation": { "required": true } } ``` * `id`: A unique identifier for the Holder Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. For React Native applications, you must create **both** an iOS and an Android Holder Application on your MATTR VII tenant, and then conditionally pass the correct configuration based on the platform OS at runtime. **Step 1: Create the iOS Holder Application** Follow the instructions in the **iOS** tab to create a Holder Application configuration for iOS. **Step 2: Create the Android Holder Application** Follow the instructions in the **Android** tab to create a Holder Application configuration for Android. **Step 3: Pass the correct configuration based on Platform OS** When initializing the SDK, use `Platform.OS` to conditionally provide the matching Holder Application configuration: ```typescript title="Example" import { Platform } from "react-native"; const holderApplicationId = Platform.OS === "ios" ? "YOUR_IOS_HOLDER_APPLICATION_ID" : "YOUR_ANDROID_HOLDER_APPLICATION_ID"; ``` * `YOUR_IOS_HOLDER_APPLICATION_ID` : The `id` returned when you created the iOS Holder Application. * `YOUR_ANDROID_HOLDER_APPLICATION_ID` : The `id` returned when you created the Android Holder Application. ### Initialize the SDK [#initialize-the-sdk] If you are using SDK Tethering, update your SDK initialization to include the platform configuration once your Holder Applications are created. This enables your app to connect to the correct MATTR VII tenant and Holder Application. If you are not using tethering, you can initialize the SDK without a `platformConfiguration`. Initialize the SDK with your platform configuration: ```swift title="Initialization" let platformConfig = PlatformConfiguration( tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, applicationId: "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) try await MobileCredentialHolder.shared.initialize( platformConfiguration: platformConfig ) ``` * `tenantHost`: The URL of your MATTR VII tenant. This must be the tenant where your iOS Holder Application is configured. * `applicationId`: The `id` of your configured iOS Holder Application. Initialize the SDK with your platform configuration: ```kotlin title="Initialization" val platformConfig = PlatformConfiguration( tenantHost = URL("https://your-tenant.vii.mattr.global"), applicationId = "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) MobileCredentialHolder.initialize(context, platformConfiguration = platformConfig) ``` * `tenantHost`: The URL of your MATTR VII tenant where your Android Holder Application is configured. * `applicationId`: The `id` of your configured Android Holder Application. Since React Native bridges both iOS and Android, and each platform has its own Holder Application registered on your MATTR VII tenant (see [Create a Holder Application](/docs/holding/sdk-getting-started#create-a-holder-application)), your initialization code must pass the correct platform-specific `applicationId` at runtime. Use `Platform.OS` to select the appropriate value: ```typescript title="Initialization" import { initialize } from "@mattrglobal/mobile-credential-holder-react-native"; import { Platform } from "react-native"; const applicationId = Platform.OS === "android" ? "your-android-holder-application-id" : "your-ios-holder-application-id"; await initialize({ platformConfiguration: { tenantHost: "https://your-tenant.vii.mattr.global", applicationId, }, }); ``` Replace the placeholder values with the `id` returned when you created each Holder Application. In practice, you would typically store these values in a configuration file or environment variables. ## Next steps [#next-steps] Your application is now initialized and ready to use the SDK. If you configured a `platformConfiguration`, it is also tethered to your MATTR VII tenant. Explore the following guides to start building: * [Credential claiming tutorial](/docs/holding/credential-claiming-tutorial): Claim a verifiable credential into your holder app. * [Remote presentation tutorial](/docs/holding/remote-presentation-tutorial): Present credentials to a web-based verifier. * [Proximity presentation tutorial](/docs/holding/proximity-presentation-tutorial): Present credentials in-person using Bluetooth. * [SDK Quickstart](/docs/holding/sdk-quickstart): Run a sample holder app end-to-end in 15-20 minutes. # SDK or ready-made wallet? URL: /docs/holding/sdk-or-wallet Before diving into integration, decide which path suits your product strategy. This page compares embedding the MATTR Pi Holder SDK against deploying the ready-made MATTR GO Hold wallet. ## SDK or ready-made wallet? [#sdk-or-ready-made-wallet] Before diving into integration, decide which path suits your product strategy. ### MATTR Pi Holder SDK: build it into your app [#mattr-pi-holder-sdk-build-it-into-your-app] Best for: teams that want credential holding embedded within their existing mobile application, with full control over the user experience and branding. The [MATTR Pi mDocs Holder SDK](/docs/holding/sdk-overview) provides native libraries for iOS, Android, and React Native. You integrate the SDK into your existing codebase, and your app gains the ability to claim, store, and present credentials directly. **Choose the SDK when you:** * Already have a mobile app and want to add credential capabilities to it. * Need full control over the user interface and interaction flow. * Want credential holding to be seamless within your existing app experience. * Need to customize presentation consent screens, credential displays, or notification handling. * Are building a purpose-specific application (e.g., employee app, government services app). ### MATTR GO Hold: use a ready-made wallet [#mattr-go-hold-use-a-ready-made-wallet] Best for: teams that want to offer credential holding without building or maintaining a custom wallet, or that need a wallet for testing and pilot deployments. [MATTR GO Hold](/docs/holding/go-hold/getting-started) is a downloadable wallet application that supports credential claiming and presentation out of the box. It can serve as a white-label starting point or a production wallet for end users. **Choose MATTR GO Hold when you:** * Want to get to market quickly without custom mobile development. * Need a wallet for pilot programs, proofs of concept, or testing. * Don't require deep integration with an existing app experience. * Want a standalone wallet app for your users. * Need a reference implementation to guide your own SDK integration later. ### Comparison [#comparison] | Consideration | MATTR Pi Holder SDK | MATTR GO Hold | | ------------------------ | --------------------------------------- | ------------------------------------ | | Integration effort | Medium–high (native SDK integration) | Low (deploy existing app) | | UX customization | Full control | Limited to configuration | | Branding | Your app, your brand | MATTR GO or white-label | | Platform support | iOS 15+, Android 7+, React Native 0.78+ | iOS 15+, Android 7+ | | Credential claiming | OID4VCI (Auth Code + Pre-authorized) | OID4VCI (Auth Code + Pre-authorized) | | Proximity presentation | ISO/IEC 18013-5 via BLE | ISO/IEC 18013-5 via BLE | | Remote presentation | ISO/IEC 18013-7 + OID4VP | ISO/IEC 18013-7 + OID4VP | | Time to first credential | Days–weeks (development cycle) | Hours (configuration only) | ## Next steps [#next-steps] If you are integrating the SDK, review the [platform requirements](/docs/holding/platform-requirements) next. # MATTR Pi mDocs Holder SDKs Overview URL: /docs/holding/sdk-overview ## Overview [#overview] The mDocs Holder SDKs are based on the [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) standard which establishes an interoperable digital representation of mobile-based credentials such as mobile drivers licenses (mDL). However, these SDKs can extend the same technology and architecture to more than just mDLs, but rather any conforming mobile document ([mDoc](/docs/concepts/mdocs)) - a term defined in ISO/IEC 18013-5. The mDocs Holder SDKs are available for React Native, iOS, and Android. They help developers add mDocs holding features to their apps, allowing users to securely claim and present mDocs in various interoperable workflows. To get started with any of our mDocs Holder SDKs, please [contact us](mailto:sales@mattr.global). ## SDK Capabilities [#sdk-capabilities] The mDocs Holder SDKs offer tools to assist developers integrating the following capabilities into their applications: * **Claim an mDoc** * Interact with a credential offer and claim an mDoc as per [OID4VCI (OpenID for Verifiable Credential Issuance)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html). The SDK supports both the [Authorization Code](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-authorization-code-flow) and [Pre-authorized Code](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-pre-authorized-code-flow) flows. * Manage a list of trusted issuers which mDoc offers can be validated against. * Store claimed mDocs and manage access to that storage. * Manage access to device keys which are bound to issued mDocs. * Use referenced Status lists to check mDocs' [revocation status](/docs/issuance/revocation/overview). * **Present an mDoc** * Present a claimed mDoc for verification via a [proximity presentation workflow](/docs/verification/in-person-overview) as per [ISO 18013-5](https://www.iso.org/standard/69084.html). * Present a claimed mDoc for verification [remotely](/docs/verification/remote-overview) as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). * **iOS 26+**: Present credentials via the Digital Credentials API for a seamless, OS-native experience without launching your app. * **Claim an mDoc** * Interact with a credential offer and claim an mDoc as per [OID4VCI (OpenID for Verifiable Credential Issuance)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html). The SDK supports both the [Authorization Code](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-authorization-code-flow) and [Pre-authorized Code](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-pre-authorized-code-flow) flows. * Manage a list of trusted issuers which mDoc offers can be validated against. * Store claimed mDocs and manage access to that storage. * Manage access to device keys which are bound to issued mDocs. * Use referenced Status lists to check mDocs' [revocation status](/docs/issuance/revocation/overview). * **Present an mDoc** * Present a claimed mDoc for verification via a [proximity presentation workflow](/docs/verification/in-person-overview) as per [ISO 18013-5](https://www.iso.org/standard/69084.html). * Present a claimed mDoc for verification [remotely](/docs/verification/remote-overview) as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). * **Claim an mDoc** * Interact with a credential offer and claim an mDoc as per [OID4VCI (OpenID for Verifiable Credential Issuance)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html). The SDK supports the [Authorization Code](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-authorization-code-flow) flow. * Manage a list of trusted issuers which mDoc offers can be validated against. * Store claimed mDocs and manage access to that storage. * Manage access to device keys which are bound to issued mDocs. * Use referenced Status lists to check mDocs' [revocation status](/docs/issuance/revocation/overview). * **Present an mDoc** * Present a claimed mDoc for verification via a [proximity presentation workflow](/docs/verification/in-person-overview) as per [ISO 18013-5](https://www.iso.org/standard/69084.html). * Present a claimed mDoc for verification [remotely](/docs/verification/remote-overview) as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). ## Supported features [#supported-features] ### ISO/IEC 18013-5 [#isoiec-18013-5] Below is a summary of [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) features supported by the mDocs Holder SDKs: | Feature | Supported options | Default | | :------------------------------ | :--------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- | | Device engagement | QR code | QR code | | Device retrieval data transport | BLE with either `mDoc peripheral server` or `mDoc central client` mode | BLE with `mDoc central client` | | Ephemeral session key curve | Any NIST P-\* keys | P-256 key using [secure enclave](https://support.apple.com/en-nz/guide/security/sec59b0b31ff/web) | | IACA public key curves | P-256, P-384, P-521 | Determined by Issuer | | Device authentication mode | Digital Signature or ECDH-agreed MAC | Digital Signature with a P-256 key using [secure enclave](https://support.apple.com/en-nz/guide/security/sec59b0b31ff/web) | | Feature | Supported options | Default | | :------------------------------ | :--------------------------------------------------------------------- | :------------------------------------------------ | | Device engagement | QR code or NFC | No default; explicit selection required | | Device retrieval data transport | BLE with either `mDoc peripheral server` or `mDoc central client` mode | BLE with `mDoc central client` | | Ephemeral session key curve | Any NIST P-\* keys | P-256 key using Keystore | | IACA public key curves | P-256, P-384, P-521 | Determined by Issuer | | Device authentication mode | Digital Signature or ECDH-agreed MAC | Digital Signature with a P-256 key using Keystore | | Feature | Supported options | Default | | :------------------------------ | :--------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | | Device engagement | QR code | QR code | | Device retrieval data transport | BLE with either `mDoc peripheral server` or `mDoc central client` mode | BLE with `mDoc peripheral server` | | Ephemeral session key curve | Any NIST P-\* keys | P-256 key using [secure enclave](https://support.apple.com/en-nz/guide/security/sec59b0b31ff/web) (iOS) / Keystore (Android) | | IACA public key curves | P-256, P-384, P-521 | Determined by Issuer | | Device authentication mode | Digital Signature or ECDH-agreed MAC | Digital Signature with a P-256 key using [secure enclave](https://support.apple.com/en-nz/guide/security/sec59b0b31ff/web) (iOS) / Keystore (Android) | ### ISO/IEC 18013-7 [#isoiec-18013-7] Below is a summary of [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) features supported by the mDocs Holder SDKs: | Feature | Options Supported | Default Option Selected | | --------------------------------- | ------------------------------------------------------- | --------------------------------- | | Data Retrieval methods | OID4VP | OID4VP | | Wallet Invocation | Custom URL and QR Code-based | Both | | MDoc Reader validation | Stored verifier certificates, client Metadata retrieval | Both | | Authorization Response Encryption | ECDH in Direct Key Agreement mode | ECDH in Direct Key Agreement mode | | Feature | Options Supported | Default Option Selected | | --------------------------------- | ------------------------------------------------------- | --------------------------------- | | Data Retrieval methods | OID4VP | OID4VP | | Wallet Invocation | Custom URL and QR Code-based | Both | | MDoc Reader validation | Stored verifier certificates, client Metadata retrieval | Both | | Authorization Response Encryption | ECDH in Direct Key Agreement mode | ECDH in Direct Key Agreement mode | | Feature | Options Supported | Default Option Selected | | --------------------------------- | ------------------------------------------------------- | --------------------------------- | | Data Retrieval methods | OID4VP | OID4VP | | Wallet Invocation | Custom URL and QR Code-based | Both | | MDoc Reader validation | Stored verifier certificates, client Metadata retrieval | Both | | Authorization Response Encryption | ECDH in Direct Key Agreement mode | ECDH in Direct Key Agreement mode | ## System requirements [#system-requirements] The iOS mDocs Holder SDK is developed in the [Swift](https://developer.apple.com/swift/) programming language and is meant for integration into iOS applications developed in Swift and/or Objective-C. Specifically it currently only supports applications developed in iOS 15 and above. **Digital Credentials API support (iOS 26+)**: The SDK supports the Digital Credentials API for presenting credentials via an OS-native overlay without launching your app. This feature requires iOS 26 or later. This SDK is developed in the [Kotlin](https://kotlinlang.org/) programming language and is meant for integration into Android applications. It currently supports Android 7 (API level 24) and above. This SDK is meant for integration into [React Native](https://reactnative.dev/) applications using React Native 0.78 and above. Supported operating systems are: * iOS 15 or higher. * Android 7 or higher. ## Dependencies [#dependencies] This section lists all dependencies for using mDocs Holder SDKs. **Third party dependencies** * [CBORCoding](https://github.com/SomeRandomiOSDev/CBORCoding) (MIT license) * [swift-certificates](https://github.com/apple/swift-certificates.git) 1.7.0 (Apache-2.0 License) * [swift-asn1](https://github.com/apple/swift-asn1) 1.3.1 (Apache-2.0 License) **Apple frameworks** * [Security](https://developer.apple.com/documentation/security) * [CryptoKit](https://developer.apple.com/documentation/cryptokit/) * [LocalAuthentication](https://developer.apple.com/documentation/localauthentication) * [AuthenticationServices](https://developer.apple.com/documentation/authenticationservices) * [CoreBluetooth](https://developer.apple.com/documentation/corebluetooth) * [Combine](https://developer.apple.com/documentation/combine) * [OSLog](https://developer.apple.com/documentation/oslog) * [UIKit](https://developer.apple.com/documentation/uikit) * [AppKit (on macOS)](https://developer.apple.com/documentation/appkit) * [IdentityDocumentServices](https://developer.apple.com/documentation/IdentityDocumentServices) * [IdentityDocumentServicesUI](https://developer.apple.com/documentation/IdentityDocumentServicesUI) **Toolchain dependencies** * Swift 5.10. * iOS support: The SDK functionality is only available for devices from iOS 15 onwards. * Xcode: The SDK was built with the latest stable Xcode available in GitHub Actions (setup-xcode action with latest-stable label), and the current version is Xcode 26.5 (17F42). * macOS 15. **Kotlin, AGP, Gradle, and Android Studio** The Android Holder SDK is built using Kotlin 2.0. This adds some intrinsic dependencies into your build tools. * Kotlin 2.0 is supported from AGP version [8.5](https://developer.android.com/build/kotlin-support). * AGP 8.5 is supported from Gradle version [8.7](https://developer.android.com/build/releases/about-agp) and Android Studio Koala [2024.1.1](https://developer.android.com/studio/releases). **Runtime** A list of runtime dependencies and licenses is generated at build time and packaged in `res/raw/dependencies_licenses.html`. The majority are Kotlin and Android, the rest are listed below: * [CBOR-Java](https://github.com/peteroupc/CBOR-Java) - [Public Domain](https://github.com/peteroupc/CBOR-Java/blob/master/LICENSE.md) * [Timber](https://github.com/JakeWharton/timber) - [Apache 2.0](https://github.com/JakeWharton/timber/blob/trunk/LICENSE.txt) **Standard libraries** * [androidx.activity:activity-ktx:1.9.0](https://developer.android.com/jetpack/androidx/releases/activity#1.9.0) * [androidx.annotation:annotation:1.8.1](https://developer.android.com/jetpack/androidx/releases/annotation#1.8.1) * [androidx.appcompat:appcompat:1.7.0](https://developer.android.com/jetpack/androidx/releases/appcompat#1.7.0) * [androidx.biometric:biometric-ktx:1.2.0-alpha05](https://developer.android.com/jetpack/androidx/releases/biometric#1.2.0-alpha05) * [androidx.browser:browser:1.8.0](https://developer.android.com/jetpack/androidx/releases/browser#1.8.0) * [androidx.core:core-ktx:1.15.0](https://developer.android.com/jetpack/androidx/releases/core#1.15.0) * [androidx.credentials:credentials-play-services-auth:1.5.0](https://developer.android.com/jetpack/androidx/releases/credentials#1.5.0) * [androidx.credentials:credentials:1.5.0](https://developer.android.com/jetpack/androidx/releases/credentials#1.5.0) * [androidx.fragment:fragment:1.5.7](https://developer.android.com/jetpack/androidx/releases/fragment#1.5.7) * [org.jetbrains.kotlin:kotlin-reflect:1.9.22](https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect/1.9.22) * [org.jetbrains.kotlin:kotlin-stdlib:2.0.0](https://kotlinlang.org/) * [org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.3) * [org.jetbrains.kotlinx:kotlinx-datetime:0.4.0](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-datetime-jvm/0.4.0) * [org.jetbrains.kotlinx:kotlinx-io-bytestring:0.6.0](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-io-bytestring-tvosarm64/0.6.0) * [org.jetbrains.kotlinx:kotlinx-io-core:0.6.0](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-io-core/0.6.0) * [org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-serialization-json/1.6.3) **Third-party libraries** * [com.jakewharton.timber:timber:5.0.1](https://mvnrepository.com/artifact/com.jakewharton.timber/timber/5.0.1) * [com.upokecenter:cbor:4.5.2](https://mvnrepository.com/artifact/com.upokecenter/cbor/4.5.2) * None. ## Versions [#versions] Below are the available versions of the mDocs Holder SDKs, including the current active version, supported versions, and those that have reached end-of-life (EOL). | Major version | Status | Latest release | End of Life date | Documentation | | ------------- | ----------- | -------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------ | | v6 | Active | 6.0.0 | - | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/6.0.0/documentation/mobilecredentialholdersdk/) | | v5 | Maintenance | 5.2.0 | 22-09-2026 | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/5.2.0/documentation/mobilecredentialholdersdk/) | | v4 | End of Life | 4.4.0 | 13-05-2026 | - | | v3 | End of Life | 3.0.0 | 7-10-2025 | - | | v2 | End of Life | 2.0.0 | 26-08-2025 | - | | v1 | End of Life | 1.0.0 | 05-05-2025 | - | | Major version | Status | Latest release | End of Life date | Documentation | | ------------- | ----------- | -------------- | ---------------- | ------------------------------------------------------------------------------------------ | | v7 | Active | 7.0.0 | - | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/7.0.0/) | | v6 | Maintenance | 6.1.4 | 22-09-2026 | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/6.1.4/) | | v5 | End of Life | 5.3.2 | 13-05-2026 | - | | v4 | End of Life | 4.1.1 | 30-12-2025 | - | | v3 | End of Life | 3.0.0 | 7-10-2025 | - | | v2 | End of Life | 2.0.0 | 26-08-2025 | - | | v1 | End of Life | 1.1.0 | 05-05-2025 | - | | Major version | Status | Latest release | End of Life date | Documentation | | ------------- | ----------- | -------------- | ---------------- | --------------------------------------------------------------------------------------------------------- | | v9 | Active | 9.0.4 | - | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/9.0.4/index.html) | | v8 | End of Life | 8.1.3 | 23-06-2026 | - | | v7 | End of Life | 7.0.0 | 12-12-2025 | - | | v6 | End of Life | 6.0.0 | 19-11-2025 | - | Release candidates (RC) are pre-release versions that may contain new features or changes that are not yet fully tested. They are intended for testing purposes, are not subject to our SLA and should not be used in production environments. # Holder SDK quickstart guide URL: /docs/holding/sdk-quickstart This quickstart is for evaluating the [MATTR Pi mDocs Holder SDKs](/docs/holding/sdk-overview). In about 15-20 minutes you will configure a sample holder mobile app (iOS, Android, or React Native), run it on a physical device and use it to: 1. **Claim a credential** issued through an OID4VCI workflow. 2. **Present that credential remotely** to a web verifier application. 3. **Present that credential in-person** to a verifier application on a different mobile device. **Estimated time: 15-20 minutes.** Use this guide as a fast evaluation path to see the Holder SDK working end-to-end on a real device. For detailed information and API examples, explore the individual tutorials and reference documentation for [credential claiming](/docs/holding/credential-claiming-tutorial), [remote presentation](/docs/holding/remote-presentation-tutorial), and [proximity presentation](/docs/holding/proximity-presentation-tutorial). ## Prerequisites [#prerequisites] * Access to the MATTR Pi mDocs Holder SDK for your chosen platform (iOS, Android, or React Native). Apply for access [here](/docs/resources/get-started). * A physical mobile device to run the holder application: * iOS/Android mobile device set up with biometric authentication and Bluetooth access. * Development environment set up for your chosen platform (e.g., Xcode for iOS, Android Studio for Android, or a React Native development environment). * Install the **MATTR GO Verify example app** on a **separate** mobile device for [iOS](https://apps.apple.com/us/app/mattr-go-verify/id6670461328) or [Android](https://play.google.com/store/apps/details?id=global.mattr.mobile.verifier). This is used for in-person verification testing. ## Evaluation Steps [#evaluation-steps] In this quickstart you will: 1. Configure and run a local sample holder project for your platform. 2. Claim a credential into the sample holder app. 3. Present that credential remotely to a web verifier. 4. Present that credential in-person to a verifier app on a different device. Use the tabs below to follow platform-specific setup steps. ### Part 1: Configure the sample holder project (5-10 minutes) [#part-1-configure-the-sample-holder-project-5-10-minutes] 1. Access the sample holder codebase by either: * Cloning the [MATTR Sample Apps repository](https://github.com/mattrglobal/sample-apps): ```bash title="Clone sample apps" git clone https://github.com/mattrglobal/sample-apps.git ``` * Or downloading just the iOS sample holder project using [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Fios-holder-tutorial-sample-app). 2. Use Xcode to open the `.xcodeproj` file in the project's folder. You can find it in the `sample-apps/ios-holder-tutorial-sample-app` directory. 3. Drag the `MobileCredentialHolderSDK-*version*.xcframework` folder (obtained from MATTR as part of the SDK package) into your project. 4. Configure `MobileCredentialHolderSDK-*version*.xcframework` to [Embed & sign](https://help.apple.com/xcode/mac/current/#/dev51a648b07). 5. Set a unique bundle identifier for the project in the Xcode project settings (e.g., `com.yourname.holdersample`). 6. Run the project on a physical iOS device (simulators do not support the required Bluetooth capabilities). 1. Access the sample holder codebase by either: * Cloning the [MATTR Sample Apps repository](https://github.com/mattrglobal/sample-apps): ```bash title="Clone sample apps" git clone https://github.com/mattrglobal/sample-apps.git ``` * Or downloading just the Android sample holder project using [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Fandroid-holder-tutorial-sample-app). 2. Open the project in Android Studio. You can find it in the `sample-apps/android-holder-tutorial-sample-app` directory. 3. Unzip the `mobile-credential-holder-*version*.zip` file (obtained from MATTR as part of the SDK package). 4. Drag the unzipped `global` folder into the project's `repo` folder. 5. Sync Project with Gradle files to recognize the new module. 6. Run the project on a physical Android device (emulators do not support the required Bluetooth capabilities). 1. Access the sample holder codebase by either: * Cloning the [MATTR Sample Apps repository](https://github.com/mattrglobal/sample-apps): ```bash title="Clone sample apps" git clone https://github.com/mattrglobal/sample-apps.git ``` * Or downloading just the React Native sample holder project using [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Freact-native-mdocs-holder-tutorial%2Freact-native-mdocs-holder-tutorial-complete). 2. Open the completed sample holder project in your code editor. You can find it in the `sample-apps/react-native-mdocs-holder-tutorial/react-native-mdocs-holder-tutorial-complete` directory. 3. Open the `app.config.ts` file. 4. Update the iOS `bundleIdentifier` to a unique value for your application, e.g. `com.mycompany.myapp`. 5. Update the Android `package` to a unique value, e.g. `com.mycompany.myapp`. 6. Navigate to the project directory and install dependencies: ```bash title="Install dependencies" yarn install ``` You must be logged in to npm with access to the MATTR Pi mDocs Holder SDK package to install dependencies successfully. If you have not yet been granted access to the SDK, apply for access [here](/docs/resources/get-started). 7. Run the following command to generate the iOS and Android project files: ```bash title="Generate native project files" npx expo prebuild ``` You should now see the `ios` and `android` folders in your project root. 8. Connect your testing device and run the following command to start the application: **iOS** ```bash title="Run iOS application" yarn ios --device ``` **Android** ```bash title="Run Android application" yarn android --device ``` ### Part 2: Claim a credential (3 minutes) [#part-2-claim-a-credential-3-minutes] Use the sample holder app you just built to claim a credential from a MATTR VII tenant. This credential will be used in the subsequent presentation steps. 1. Open the MATTR Labs [Pre-authorized Offer tool](https://tools.mattrlabs.com/issue-credential). 2. Select the **Generate Credential Offer** button.\ A QR code will be generated and rendered on the screen, along with a transaction code. 3. Run your application. 4. Select the **Claim Credential** button. 5. Scan the QR code. 6. Follow any on-screen instructions to claim the credential. Under the hood, the sample app uses the Holder SDK to discover the credential offer from the QR code, and then claim and store the credential on the device using the OID4VCI [Pre-authorized Code flow](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-pre-authorized-code-flow). ### Part 3: Present the credential remotely (5 minutes) [#part-3-present-the-credential-remotely-5-minutes] Present the credential you just claimed to a web verifier application using the [remote presentation workflow](/docs/verification/remote-overview) defined by [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). 1. Use a **mobile browser on the device** where the sample application is installed to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/mdoc-online-presentation). 2. Select the **OID4VP (Redirect)** option from the *Select Experience* list. 3. Select **Request credentials**. 4. Select **Allow** to open the sample application. 5. Select the credential you claimed in the previous part. 6. Select **Send Response**. 7. You should be redirected back to the MATTR Labs remote presentation testing tool, where you will see a successful verification indication. 1. Use a **desktop browser** to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). 2. Select **OID4VP (Redirect)** from the *Select Experience* list. 3. Select **Request credentials**. 4. Open the camera on the device where the sample application is installed and scan the QR code. 5. Confirm opening the QR code with your sample application (this message might vary depending on your device and platform). 6. Select the credential you claimed in the previous part. 7. Select **Send Response**. 8. Back on your desktop browser, you should see a successful verification indication. In these flows, the sample app uses the Holder SDK to respond to the OID4VP authorization request, as defined in the OID4VP specification and ISO/IEC 18013-7:2025. ### Part 4: Present the credential in-person (3 minutes) [#part-4-present-the-credential-in-person-3-minutes] Present the credential you claimed to a verifier application on a different mobile device using the [proximity presentation workflow](/docs/verification/in-person-overview) as defined by [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). Once you have the sample holder app running on one device, and the MATTR GO Verify app installed on a separate device, follow these steps to test in-person presentation: 1. On the **sample holder app**, select **Present Credentials**. 2. The app will display a QR code containing the device engagement string. 3. On the **verifier device**, launch the MATTR GO Verify app, select **Verify** and scan the QR code from the holder app. 4. On the **sample holder app**, review the verifier's request and consent to share the credential. 5. The verifier application will display the verification results. Behind the scenes, the Holder SDK handled device engagement, established a secure BLE session, and presented the mDoc to the verifier as per ISO/IEC 18013-5:2021. ## Review the codebase [#review-the-codebase] The sample holder application uses the [Holder SDK](/docs/holding/sdk-overview) to claim and present mDocs. Once you have the sample application running, use this section to understand the key SDK calls you will reuse in your own application. ### Initialize the SDK [#initialize-the-sdk] ```swift title="Initialize the SDK" try await mobileCredentialHolder.initialize( userAuthenticationConfiguration: UserAuthenticationConfiguration( // [!code highlight] userAuthenticationBehavior: .always, // [!code highlight] userAuthenticationType: .biometricOrPasscode, // [!code highlight] presentationTimeoutSeconds: 20 // [!code highlight] ), credentialIssuanceConfiguration: CredentialIssuanceConfiguration( redirectUri: "", // [!code highlight] autoTrustMobileCredentialIaca: true // [!code highlight] ), dcConfiguration: DCConfiguration( // iOS 26+ only // [!code highlight] appGroup: "group.com.yourcompany.app", // [!code highlight] supportedDocTypes: [.mDL, .eudi] // [!code highlight] ) // [!code highlight] ) ``` * `userAuthenticationConfiguration`: Optional. Configures when the SDK requires user authentication to access or present credentials. The example shows the default values. Refer to the [reference documentation](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/userauthenticationconfiguration) for additional details about the available configuration options. * `redirectUri` : Provide a URL that corresponds to a specific path in your application. The SDK will redirect the user to this path after they successfully complete authentication. Required for the [`Authorization Code flow`](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-authorization-code-flow). * `autoTrustMobileCredentialIaca` : Set to `true` if you trust the credential issuer(s) for any offer. Defaults to `false`. * `dcConfiguration`: **Optional, iOS 26+ only**. Configures Digital Credentials API support for presenting credentials via an OS-native overlay. Specify your app group and supported document types (e.g., `.mDL`, `.eudi`, `.euav`, `.photoid`, `.jpMnc`). ```kotlin title="Initialize the SDK" mobileCredentialHolder.initialize( activity, userAuthenticationConfiguration = UserAuthenticationConfiguration( // [!code highlight] behavior = UserAuthenticationBehavior.Always, // [!code highlight] presentationTimeoutSeconds = 20 // [!code highlight] ), credentialIssuanceConfiguration = CredentialIssuanceConfiguration( redirectUri = "", // [!code highlight] autoTrustMobileCredentialIaca = true // [!code highlight] ) ) ``` * `userAuthenticationConfiguration`: Optional. Configures when the SDK requires user authentication to access or present credentials. The example shows the default values. Refer to the [reference documentation](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-user-authentication-configuration/index.html) for additional details about the available configuration options. * `redirectUri` : Provide a URL that corresponds to a specific path in your application. The SDK will redirect the user to this path after they successfully complete authentication. Required for the [`Authorization Code flow`](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-authorization-code-flow). * `autoTrustMobileCredentialIaca` : Set to `true` if you trust the credential issuer(s) for any offer. Defaults to `false`. ```ts title="Initialize the SDK" const initializeResult = await mobileCredentialHolder.initialize({ userAuthenticationConfiguration: { // [!code highlight] userAuthenticationBehavior: UserAuthenticationBehavior.OnInitialize, // [!code highlight] userAuthenticationType: UserAuthenticationType.BiometricOrPasscode, // [!code highlight] }, credentialIssuanceConfiguration: { // [!code highlight] autoTrustMobileCredentialIaca: true, // [!code highlight] redirectUri: "", // [!code highlight] }, }); if (initializeResult.isErr()) { const { error } = initializeResult; // handle error scenarios return; } ``` * `userAuthenticationConfiguration`: Optional. Configures when the SDK requires user authentication to access or present credentials. Refer to the [reference documentation](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/UserAuthenticationConfiguration.html) for additional details about the available configuration options. * `redirectUri`: Provide a URL that corresponds to a specific path in your application. The SDK will redirect the user to this path after they successfully complete authentication. Required for the [`Authorization Code flow`](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-authorization-code-flow). * `autoTrustMobileCredentialIaca`: Set to `true` if you trust the credential issuer(s) for any offer. Defaults to `false`. ### Credential claiming workflow [#credential-claiming-workflow] #### Discover credential offer [#discover-credential-offer] ```swift title="Discover credential offer" let discoveredOffer = try await mobileCredentialHolder.discoverCredentialOffer(offerUrl: String) ``` * `offerUrl` : Pass the OID4VCI initiation URL. The [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/discovercredentialoffer\(_:\)) method returns a [`DiscoveredCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/discoveredcredentialoffer) object containing the offer details. ```kotlin title="Discover credential offer" val discoveredOffer = mobileCredentialHolder.discoverCredentialOffer(offerUri) ``` * `offerUrl` : Pass the OID4VCI initiation URL as a string. The [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/discover-credential-offer.html?query=suspend%20fun%20discoverCredentialOffer\(offer:%20String\):%20DiscoveredCredentialOffer) method returns an instance of [`DiscoveredCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.issuance.dto/-discovered-credential-offer/index.html) containing the offer details. ```ts title="Discover credential offer" const discoveredOfferResult = await mobileCredentialHolder.discoverCredentialOffer(offerUrl); // [!code highlight] if (discoveredOfferResult.isErr()) { const { error } = discoveredOfferResult; // handle error scenarios return; } const credentialOffer = discoveredOfferResult.value; ``` * `offerUrl` : Pass the OID4VCI initiation URL. The `discoverCredentialOffer` method returns a `Result<`CredentialOfferResponse`, Error>` (using the [`neverthrow`](https://www.npmjs.com/package/neverthrow) pattern) object containing the offer details and/or any errors. #### Claim and store credentials [#claim-and-store-credentials] ```swift title="Claim credentials" let retrievedCredentialResults = try await mobileCredentialHolder.retrieveCredentials( credentialOffer: String, // [!code highlight] clientId: "", // [!code highlight] transactionCode: nil // [!code highlight] ) ``` * `credentialOffer` : Pass the OID4VCI credential offer URI. * `clientId` : Pass a string that identifies your wallet application with the issuer. * `transactionCode` : Provide a transaction code if the [`Pre-Authorization Code flow`](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-pre-authorized-code-flow) offer requires a transaction code. The [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/retrievecredentials\(credentialoffer:clientid:transactioncode:\)) method returns an array of [`RetrieveCredentialResult`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/retrievecredentialresult) objects, each containing the metadata of the retrieved credentials, including the credential ID (`credentialId`). ```kotlin title="Claim credentials" val credentialResults = mobileCredentialHolder.retrieveCredentials( activity = activity, credentialOffer = offerUri, // [!code highlight] clientId = "", // [!code highlight] transactionCode = null // [!code highlight] ) ``` * `credentialOffer` : Pass the OID4VCI credential offer URI. * `clientId` : Pass a string that identifies your wallet application with the issuer. * `transactionCode` : Provide a transaction code if claiming a credential using a [`Pre-authorized Code flow`](/docs/issuance/pre-authorized-code/overview) that requires a transaction code. The [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/retrieve-credentials.html?query=suspend%20fun%20retrieveCredentials\(activity:%20Activity,%20credentialOffer:%20String,%20clientId:%20String,%20transactionCode:%20String?%20=%20null\):%20List%3CRetrieveCredentialResult%3E) method returns an array of [`RetrieveCredentialResult`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.issuance.dto/-retrieve-credential-result/index.html) objects, each containing the metadata of the retrieved credentials, including the credential ID (`credentialId`). ```ts title="Claim credentials" const retrievedCredentialsResults = await mobileCredentialHolder.retrieveCredentials({ credentialOffer: offerUrl, // [!code highlight] clientId: "", // [!code highlight] transactionCode: undefined, // [!code highlight] }); if (retrievedCredentialsResults.isErr()) { const { error } = retrievedCredentialsResults; // handle error scenarios return; } const retrievedCredentials = retrievedCredentialsResults.value; const retrievedCredential = retrievedCredentials[0]; if (!retrievedCredential?.isSuccess) { // handle error scenarios (retrievedCredential.error / retrievedCredential.docType) return; } const credentialId = retrievedCredential.credentialId; ``` * `credentialOffer`: Pass the OID4VCI credential offer URI. * `clientId`: Pass a string that identifies your wallet application with the issuer. * `transactionCode`: Provide a transaction code if the [`Pre-Authorization Code flow`](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-pre-authorized-code-flow) offer requires a transaction code. The `retrieveCredentials` method returns a `Result` (using the [`neverthrow`](https://www.npmjs.com/package/neverthrow) pattern) object containing the metadata of the retrieved credentials, including the credential ID (`credentialId`). #### Access claimed credentials [#access-claimed-credentials] ```swift title="Access credential" let credential = try mobileCredentialHolder.getCredential(credentialId: String) ``` * `credentialId` : Pass the credential ID of the credential you want to access. ```kotlin title="Access credential" val credential = mobileCredentialHolder.getCredential(credentialId) ``` * `credentialId` : Pass the credential ID of the credential you want to access, as a string. ```tsx title="Access credential" const credentialResult = await mobileCredentialHolder.getCredential(credentialId); // [!code highlight] if (credentialResult.isErr()) { const { error } = credentialResult; // handle error scenarios return; } const credential = credentialResult.value; ``` * `credentialId` : Pass the credential ID of the credential you want to access. ### Remote presentation handling [#remote-presentation-handling] #### Register the verifier's Authorization URL [#register-the-verifiers-authorization-url] Ensure that your application is registered to [handle the verifier's authorization URL](/docs/holding/remote-presentation-tutorial#step-1-register-the-verifiers-authorization-endpoint). This is typically done in the app's `Info.plist` file. The URL scheme should match the one used by the verifier. Ensure that your application is registered to [handle the verifier's authorization URL](/docs/holding/remote-presentation-tutorial#register-the-verifiers-authorization-endpoint). This is typically done in the AndroidManifest.xml file. The URL scheme should match the one used by the verifier. Ensure that your application is registered to handle the verifier's authorization URL. The URL scheme should match the one used by the verifier. * For [iOS](/docs/holding/remote-presentation-tutorial#step-1-register-the-verifiers-authorization-endpoint) this is typically done in the app's `Info.plist` file. * For [Android](/docs/holding/remote-presentation-tutorial#register-the-verifiers-authorization-endpoint) this is typically done in the `AndroidManifest.xml` file. #### Create an online presentation session [#create-an-online-presentation-session] ```swift title="Create an online presentation session" let onlinePresentationSession = try await mobileCredentialHolder.createOnlinePresentationSession(authorizationRequestUri: authorizationRequestURI, requireTrustedVerifier: false) ``` * `authorizationRequestURI` : Pass the OID4VP authorization request URI as a string. * `requireTrustedVerifier` : Set to `true` if you want to ensure that the verifier exists on the app's trusted verifier's list. The [`createOnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/createonlinepresentationsession\(authorizationrequesturi:requiretrustedverifier:deviceauthenticationoption:\)) method returns an instance of [`OnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/onlinepresentationsession) containing requested and matching credentials. ```kotlin title="Create an online presentation session" val onlinePresentationSession = mobileCredentialHolder.createOnlinePresentationSession( authorisationRequestUri = authorizationRequestUri, requireTrustedVerifier = false ) ``` * `authorisationRequestURI` : Pass the OID4VP authorization request URI as a string. * `requireTrustedVerifier` : Set to `true` if you want to ensure that the verifier exists on the app's trusted verifier's list. The [`createOnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/create-online-presentation-session.html?query=fun%20createOnlinePresentationSession\(authorisationRequestUri:%20String,%20requireTrustedVerifier:%20Boolean%20=%20false\):%20OnlinePresentationSession) method returns an instance of [`OnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.onlinepresentation/-online-presentation-session/index.html) containing requested and matching credentials. ```ts title="Create an online presentation session" const createSessionResult = await mobileCredentialHolder.createOnlinePresentationSession({ authorizationRequestUri: authorizationRequestURI, requireTrustedVerifier: false, }); if (createSessionResult.isErr()) { const { error } = createSessionResult; // handle error scenarios return; } const onlinePresentationSession = createSessionResult.value; ``` * `authorizationRequestUri` : Pass the OID4VP authorization request URI as a string. * `requireTrustedVerifier` : Set to `true` if you want to ensure that the verifier exists on the app's trusted verifier's list. The `createOnlinePresentationSession` method returns an instance of `OnlinePresentationSession` containing requested and matching credentials. #### Present matching credentials to holder [#present-matching-credentials-to-holder] ```swift title="Present matching credentials to the holder" let credential = try await mobileCredentialHolder.getCredential(credentialId: id) ``` * `id` : Pass the identifier of the credential you want to present, as a string. This information can be retrieved from the [`OnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/onlinepresentationsession) object returned by the [`createOnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/createonlinepresentationsession\(authorisationrequesturi:requiretrustedverifier:\)) method. Your application must then gather the user's consent to share the matching credential with the verifier. ```kotlin title="Present matching credentials to the holder" val credential = mobileCredentialHolder.getCredential(credentialId) ``` * `credentialId` : Pass the identifier of the credential you want to present, as a string. This information can be retrieved from the [`OnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.onlinepresentation/-online-presentation-session/index.html) object returned by the [`createOnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/create-online-presentation-session.html?query=fun%20createOnlinePresentationSession\(authorisationRequestUri:%20String,%20requireTrustedVerifier:%20Boolean%20=%20false\):%20OnlinePresentationSession) method. Your application must then gather the user's consent to share the matching credential with the verifier. ```ts title="Present matching credentials to the holder" const credentialResult = await mobileCredentialHolder.getCredential( credentialId, // [!code highlight] ); if (credentialResult.isErr()) { const { error } = credentialResult; // handle error scenarios return; } const credential = credentialResult.value; ``` * `credentialId` : Pass the identifier of the credential you want to present, as a string. This information can be retrieved from the `OnlinePresentationSession` object returned by the method. Your application must then gather the user's consent to share the matching credential with the verifier. #### Send a response to the verifier [#send-a-response-to-the-verifier] ```swift title="Send response to the verifier" try await onlinePresentationSession.sendResponse(credentialIds: [id]) ``` * `id` : Pass the identifier of the credential you want to send, as a string. ```kotlin title="Send response to the verifier" onlinePresentationSession.sendResponse( credentialIds = listOf(credentialId), // [!code highlight] activity = activity ) ``` * `credentialId` : Pass the identifier of the credential you want to send, as a string. ```ts title="Send response to the verifier" const sendResponseResult = await onlinePresentationSession.sendResponse({ credentialIds: [credentialId], }); if (sendResponseResult.isErr()) { const { error } = sendResponseResult; // handle error scenarios return; } ``` * `credentialId` : Pass the identifier of the credential you want to send, as a string. ### Proximity presentation handling [#proximity-presentation-handling] #### Create a proximity presentation session [#create-a-proximity-presentation-session] ```swift title="Create a proximity presentation session" let proximityPresentationSession = try await mobileCredentialHolder.createProximityPresentationSession( onRequestReceived: onRequestReceived(_:error:) // 1. Callback triggered when the verifier requests a credential for verification. Use this callback to display the request on the UI and get the user's consent to share any matched credentials. ) ``` The [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/createproximitypresentationsession\(onrequestreceived:onconnected:onsessionterminated:deviceauthenticationoption:blemode:\)) method returns an instance of [`ProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/proximitypresentationsession) which contains a `deviceEngagement` property that must be shared with the verifier embedded in a QR code to [establish a secure connection](/docs/verification/in-person-overview#engagement-phase). ```kotlin title="Create a proximity presentation session" val proximityPresentationSession = mobileCredentialHolder.createProximityPresentationSession( onRequestReceived = { session, matchedCredentials, error -> if (error != null) { // Handle error } else { // 1. Show request on UI // 2. Show matched credentials on UI and get user's consent to share them // 3: session.sendResponse( val credentialIds = matchedCredentials!!.flatMap { it.matchedCredentials.map { credential -> credential.id } } , activity = activity ) } } ) ``` The [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/create-proximity-presentation-session.html?query=suspend%20fun%20createProximityPresentationSession\(activity:%20Activity,%20deviceAuthenticationOption:%20DeviceAuthenticationOption%20=%20DeviceAuthenticationOption.Signature,%20bleMode:%20BleMode%20=%20BleMode.MDocClientCentral,%20onRequestReceived:%20ProximityPresentationSession.OnRequestReceived,%20onConnected:%20ProximityPresentationSession.OnConnected?%20=%20null,%20onSessionTerminated:%20ProximityPresentationSession.OnSessionTerminated?%20=%20null\):%20ProximityPresentationSession) method returns an instance of [`ProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-proximity-presentation-session/index.html) which contains a `deviceEngagement` property that must be shared with the verifier embedded in a QR code to [establish a secure connection](/docs/verification/in-person-overview#engagement-phase). ```ts title="Create a proximity presentation session" const createSessionResult = await mobileCredentialHolder.createProximityPresentationSession({ onRequestReceived: (data) => { if ("error" in data) { // handle error scenarios return; } // 1. Show UI prompt and gather user consent // 2. Retrieve matching credential from request object }, }); if (createSessionResult.isErr()) { const { error } = createSessionResult; // handle error scenarios return; } const proximityPresentationSession = createSessionResult.value; ``` The `createProximityPresentationSession` method returns an instance of `ProximityPresentationSession` which contains a `deviceEngagement` property that must be shared with the verifier embedded in a QR code to [establish a secure connection](/docs/verification/in-person-overview#engagement-phase). #### Present matching credentials to holder [#present-matching-credentials-to-holder-1] ```swift title="Present matching credentials to the holder" let credential = try await mobileCredentialHolder.getCredential(credentialId: id) ``` * `id` : Pass the identifier of the credential you want to present, as a string. This information is returned by the [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/createproximitypresentationsession\(onrequestreceived:onconnected:onsessionterminated:deviceauthenticationoption:blemode:\)) method on a `onRequestReceived` event, triggered when the verifier requests a credential for verification. Your application must then gather the user's consent to share the matching credential with the verifier. ```kotlin title="Present matching credentials to the holder" val credential = mobileCredentialHolder.getCredential(credentialId) ``` * `credentialId` : Pass the identifier of the credential you want to present, as a string. This information is returned by the [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/create-proximity-presentation-session.html?query=suspend%20fun%20createProximityPresentationSession\(activity:%20Activity,%20deviceAuthenticationOption:%20DeviceAuthenticationOption%20=%20DeviceAuthenticationOption.Signature,%20bleMode:%20BleMode%20=%20BleMode.MDocClientCentral,%20onRequestReceived:%20ProximityPresentationSession.OnRequestReceived,%20onConnected:%20ProximityPresentationSession.OnConnected?%20=%20null,%20onSessionTerminated:%20ProximityPresentationSession.OnSessionTerminated?%20=%20null\):%20ProximityPresentationSession) method on a `onRequestReceived` event, triggered when the verifier requests a credential for verification. Your application must then gather the user's consent to share the matching credential with the verifier. ```ts title="Present matching credentials to the holder" const credentialResult = await mobileCredentialHolder.getCredential(credentialId); // [!code highlight] if (credentialResult.isErr()) { const { error } = credentialResult; // handle error scenarios return; } const credential = credentialResult.value; // // Send a response to the verifier // const sendResponseResult = await mobileCredentialHolder.sendProximityPresentationResponse({ credentialIds: [credentialId], terminateSession: false, }); if (sendResponseResult.isErr()) { const { error } = sendResponseResult; // handle error scenarios return; } ``` * `credentialId` : Pass the identifier of the credential you want to present, as a string. This information is returned by the `createProximityPresentationSession` method on a `onRequestReceived` event, triggered when the verifier requests a credential for verification. Your application must then gather the user's consent to share the matching credential with the verifier. #### Send a response to the verifier [#send-a-response-to-the-verifier-1] ```swift title="Send response to the verifier" try await proximityPresentationSession.sendResponse(credentialIds: [id]) ``` * `id` : Pass the identifier of the credential you want to send, as a string. ```kotlin title="Send response to the verifier" proximityPresentationSession?.sendResponse(credentialIds = listOf(credentialId)) ``` * `credentialId` : Pass the identifier of the credential you want to send, as a string. ```ts title="Send response to the verifier" const sendResponseResult = await mobileCredentialHolder.sendProximityPresentationResponse({ credentialIds: [credentialId], }); if (sendResponseResult.isErr()) { const { error } = sendResponseResult; // handle error scenarios return; } ``` * `credentialId` : Pass the identifier of the credential you want to send, as a string. ## Next steps [#next-steps] * Explore the individual tutorials for detailed step-by-step instructions: * [Credential claiming tutorial](/docs/holding/credential-claiming-tutorial) * [Remote presentation tutorial](/docs/holding/remote-presentation-tutorial) * [Proximity presentation tutorial](/docs/holding/proximity-presentation-tutorial) * Review the [mDocs Holder SDK overview](/docs/holding/sdk-overview) for platform support and capabilities. * Use the sample app to [claim a credential](/docs/holding/go-hold/getting-started#claim-a-credential) via an Authorization Code flow to experience this flow. # Trust and security considerations URL: /docs/holding/trust-and-security Holding credentials securely means managing which verifiers and issuers your app trusts, authenticating the holder, and keeping credential status current. This page covers each of those considerations. ## Trust and security considerations [#trust-and-security-considerations] ### Trusted verifier management [#trusted-verifier-management] The Holder SDK supports configurable trust lists for verifier certificates. You can adapt your app's behavior based on trust level: * Block requests from untrusted verifiers entirely. * Show warnings or reduced information for unrecognized verifiers. * For remote presentations, verifier authentication is mandatory. The SDK validates the verifier's identity before proceeding. ### Trusted issuer management [#trusted-issuer-management] The SDK maintains a local list of trusted issuer certificates. Only credentials from issuers whose signing certificates are in your trust list will pass validation. Configure this list based on the jurisdictions and credential types your app supports. ### Holder authentication [#holder-authentication] The SDK leverages platform-level security to ensure credentials are only presented by their rightful holder: * **Biometric authentication**: require Face ID, Touch ID, or fingerprint before credential access. * **Device unlock**: ensure the device has a passcode/PIN set. * **Key binding**: credential keys are bound to the device's secure element or keystore. * **Per-credential policies**: issuers can define security policies per credential type. Balance security with inclusion: some users may lack or have disabled biometrics. The SDK supports OS-level alternatives (PIN, passcode, pattern) as fallbacks. See the [Device Key Authentication guide](/docs/holding/credential-claiming-guides/device-key-authentication-guide) for detailed configuration. ### Status list management [#status-list-management] Credentials can be revoked after issuance. The SDK handles status list retrieval and caching based on the issuer's configured `ttl` and `exp` parameters: * **`ttl`**: recommended duration for using a cached status list before fetching an update. * **`exp`**: hard deadline after which a cached status list must not be used. Apps can rely on cached statuses between `ttl` and `exp` to maintain offline usability. Consider how your app communicates credential status changes to the user (e.g., badge indicators, notifications). See [credential revocation](/docs/issuance/revocation/overview) for background on status lists. ## Next steps [#next-steps] Next, review the [operational tooling](/docs/holding/operational-tooling) available for your integration. # Choose your issuance workflow URL: /docs/issuance/choosing-a-workflow The first decision when building an issuance solution is which OID4VCI workflow to use. MATTR VII supports two, and your choice depends on how much control you need over user authentication during the claiming process. ## Choose your issuance workflow [#choose-your-issuance-workflow] MATTR VII supports two OID4VCI workflows. Your choice depends on how much control you need over user authentication during the claiming process. ### Pre-authorized Code flow [#pre-authorized-code-flow] Best for: issuing to known users, silent issuance within existing app sessions, high-assurance credentials where identity has already been verified out-of-band. The issuer authenticates the user externally, prepares the credential data, and creates a single-use offer. The wallet claims the credential without an additional authentication step. * Offers are **single-use**, consumed after successful claim. * Supports [mDocs](/docs/concepts/mdocs) credential format. * Optional transaction code adds two-factor security. * Ideal for in-app issuance or silent credential updates. **Components involved:** | Component | Role | | ---------------------------------------------------------------------------- | ----------------------------------------------- | | [Credential offer](/docs/issuance/credential-offer/overview) | Contains pre-authorized code and claim data | | [Credential configuration](/docs/issuance/credential-configuration/overview) | Defines credential structure and claim mappings | | [Claims source](/docs/issuance/claims-source/overview) (optional) | Enriches credential with additional data | See the [Pre-authorized Code flow overview](/docs/issuance/pre-authorized-code/overview) for the detailed workflow and configuration steps. ### Authorization Code flow [#authorization-code-flow] Best for: public-facing credential offers, user self-service portals, scenarios requiring real-time identity verification. The holder scans a QR code or clicks a link, authenticates through your identity provider, and the credential is issued upon successful authentication. * Offers are **reusable**: Multiple users can claim from the same offer. * Supports [mDocs](/docs/concepts/mdocs) and [CWT](/docs/concepts/cwt) credential formats. * Integrates with your existing OIDC identity provider. * Supports optional [interaction hooks](/docs/issuance/optional-components#interaction-hooks) for additional verification steps. **Components involved:** | Component | Role | | --------------------------------------------------------------------------------------------- | ----------------------------------------------- | | [Credential offer](/docs/issuance/credential-offer/overview) | Entry point that starts the flow | | [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) | Authenticates the holder via OIDC | | [Credential configuration](/docs/issuance/credential-configuration/overview) | Defines credential structure and claim mappings | | [Claims source](/docs/issuance/claims-source/overview) (optional) | Fetches additional data from external systems | | [Interaction hook](/docs/issuance/authorization-code/interaction-hook/overview) (optional) | Adds custom steps between auth and issuance | See the [Authorization Code flow overview](/docs/issuance/authorization-code/overview) for the detailed workflow and configuration steps. ## Next steps [#next-steps] Once you have chosen a workflow, [define your credential structure](/docs/issuance/defining-credential-structure). # Manage credential lifecycle URL: /docs/issuance/credential-lifecycle Once issued, credentials may need their status updated and their usage monitored. This page covers revocation through published status lists and monitoring issuance activity with analytics. ## Manage credential lifecycle [#manage-credential-lifecycle] Once issued, credentials may need their status updated. MATTR VII supports revocation through published status lists. ### Revocation [#revocation] * Configure revocation by enabling `includeStatus` in your credential configuration. * MATTR VII publishes a [status list](/docs/issuance/revocation/overview#status-list) that verifiers can retrieve. * Control caching behavior with two parameters: * **Time to Live (`ttl`)**: recommended duration for using a retrieved copy of the status list before fetching the latest version. Defaults to one day. * **Expiry (`exp`)**: hard deadline after which a retrieved status list must not be used. Defaults to seven days. * Wallets and verifiers can rely on cached statuses between `ttl` and `exp` to maintain offline usability. Setting appropriate values balances up-to-date status checking with user experience. * Consider how to notify users if their credentials are revoked. ### Monitoring with analytics [#monitoring-with-analytics] Track issuance activity using MATTR VII's [Analytics API](/docs/platform-management/analytics/overview): * Monitor issuance volume and success rates. * Identify failures (timeout, claim source errors, authentication failures). * Set up webhooks for real-time event streaming to your monitoring tools. See the [analytics setup guide](/docs/platform-management/analytics/guide) for configuration. ## Next steps [#next-steps] Review the [frequently asked questions](/docs/issuance/faq) for answers to common issuance questions. # How to issue a CWT credential directly URL: /docs/issuance/cwt-direct-issuance [CWT credentials](/docs/concepts/cwt) represent claims of data that can be cryptographically proven as authentic while being compact enough to fit inside a QR code. This guide will show you how to create and format a new CWT or Semantic CWT credential. This guide can be used to create both CWT and Semantic CWT credentials. Any differences in the workflow are highlighted throughout the guide. ## Prerequisites [#prerequisites] * DIDs: * Issuer DID: This is a [`did:web`](/docs/concepts/dids) that identifies the issuer who attests the claims in the credential are accurate. * You can only sign a CWT credential using DIDs with a `P-256` key type. MATTR VII creates `did:web`s with this key type (and others) by default. * [Claim values](/docs/concepts/cwt/data): The claims you wish to include in the credential. This is the information attested by the issuer. * The identifier of the desired [PDF](/docs/issuance/cwt-credential-templates/pdf-templates), [Apple digital pass](/docs/issuance/cwt-credential-templates/apple-templates) or [Google digital pass](/docs/issuance/cwt-credential-templates/google-templates) template. ## Overview [#overview] Direct issuance of a CWT credential comprises the following steps: 1. [Sign the CWT credential](#sign-the-cwt-credential). 2. [Format the signed CWT credential](#format-the-signed-cwt-credential). 3. [Share the formatted credential](#share-the-formatted-credential). ### Sign the CWT credential [#sign-the-cwt-credential] The request to sign the credential is different when you're signing a CWT or Semantic CWT Credential. Make a request of the following structure to [create and sign a new CWT credential](/docs/issuance/direct-issuance-api-reference/cwt-issuance#sign-a-cwt-credential): ```http title="Request" POST /v2/credentials/compact/sign ``` ```json title="Request body" { "payload": { "iss": "did:web:learn.vii.au01.mattr.global", "nbf": 1704099600, "exp": 1767258000, "type": "Course Credential", "name": "Emma Jane Tasma", "code": "HS.278", "certificationName": "Working at Heights", "certificationLevel": "Level 4", "issuerName": "Advanced Safety Training", "expiry": "2026-01-01" }, "revocable": true, "isRevoked": false } ``` * `iss` : Use the Issuer DID. This must be a publicly available and resolvable `did:web` for the credential to be valid and verifiable. * `nbf` : Not before. When set, credential verification will fail if the current time is earlier than the nbf value. Must be expressed as a Unix timestamp. * `exp` : Expiry. When set, credential verification will fail if the current time is later than the exp value. Must be expressed as a Unix timestamp. * `type` : Credential type. * `name` : Example for a custom claim. * `code` : Example for a custom claim. * `certificationName` : Example for a custom claim. * `certificationLevel` : Example for a custom claim. * `issuerName` : Example for a custom claim. * `revocable` : When set to `true`, the signed credential can later be [revoked](/docs/issuance/revocation/overview). When set to `false`, the credential cannot be revoked. Defaults to `false`. * `isRevoked`: When set to `true`, the signed credential is issued as [revoked](/docs/issuance/revocation/overview), and must be unrevoked to become valid. If `isRevoked` is provided (e.g. set to either `true` or `false`), `revocable` must be set to `true`. Defaults to `false`. *Response* ```json title="Response body" { "id": "bKcrxojFSuSZvI5qhKInxA", // [!code highlight] "decoded": { "iss": "did:web:learn.vii.au01.mattr.global", "nbf": 1704099600, "type": "Course Credential", "exp": 1767258000, "name": "Emma Jane Tasma", "code": "HS.278", "certificationName": "Working at Heights", "certificationLevel": "Level 4", "issuerName": "Advanced Safety Training", "status": { // [!code highlight] "index": 3, // [!code highlight] "url": "https://learn.vii.au01.mattr.global/v2/credentials/compact/revocation-lists/887cd140-e4d7-4518-b70f-305b23778848" // [!code highlight] }, "jti": "bKcrxojFSuSZvI5qhKInxA" // [!code highlight] }, "encoded": "CSC:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSALWVQAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQHIAACAACOFBW65LSONSSAQ3SMVSGK3TUNFQWYBA2NFLDPEDENZQW2ZLPIVWW2YJAJJQW4ZJAKRQXG3LBMRRW6ZDFMZEFGLRSG44HCY3FOJ2GSZTJMNQXI2LPNZHGC3LFOJLW64TLNFXGOIDBOQQEQZLJM5UHI43SMNSXE5DJMZUWGYLUNFXW4TDFOZSWYZ2MMV3GK3BAGRVGS43TOVSXETTBNVSXQGCBMR3GC3TDMVSCAU3BMZSXI6JAKRZGC2LONFXGOZTFPBYGS4TZNIZDAMRWFUYDCLJQGE5AAAIAACRAEAYDPB2WQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UF5ZGK5TPMNQXI2LPNYWWY2LTORZS6OBYG5RWIMJUGAWWKNDEG4WTINJRHAWWENZQMYWTGMBVMIZDGNZXHA4DIOAH3BAFA3FHFPDIRRKK4SM3ZDTKQSRCPRCYIA7RFUZYQI3RIGDHIGLAODJ6K2F245DTLIIKXAD35TORFQ7MVRJCIEPH6SC6NGA4HRMK76H5V6GXP66FFNX7MNYC6MYVU7ZLLXYVLXBU" // [!code highlight] } ``` * `id` : Unique credential identifier. This is required when [revoking a credential](/docs/issuance/revocation/overview). * `decoded` : This is the decoded version of the credential. It includes all the information provided in the request, with the addition of the following elements: * `status` : If `revocable` was set to `true` in the request, this object is used to provide information required for revocation: * `index` : This indicates the index of this specific credential within the revocation list. * `url` : References the revocation List which holds the revocation status for this specific credential. * `jti` : This JWT ID identifies this credential and is identical to the `id` element. When `revocable` is set to `true`, this value is persisted on the tenant. * `encoded` : The base32 encoded string representation of the CWT credential. `CSC` stands for `COSE Sign Compact` profile. You will use this element to convert the credential into a [format](#format-the-signed-cwt-credential) that can be shared with a holder. Semantic CWT Credentials represent claims inside a [W3C Verifiable Credential](https://www.w3.org/TR/vc-data-model) data model which is then signed as a CWT credential. Make a request of the following structure to [create and sign a new Semantic CWT credential](/docs/issuance/direct-issuance-api-reference/semantic-cwt-issuance#sign-a-semantic-cwt-credential): ```http title="Request" POST /v2/credentials/compact-semantic/sign ``` ```json title="Request body" { "payload": { "iss": "did:web:learn.vii.au01.mattr.global", "nbf": 1704099600, "exp": 1767258000, "vc": { "type": "Course Credential", "credentialSubject": { "name": "Emma Tasma", "code": "HS.278", "certificationName": "Working at Heights", "certificationLevel": "Level 4", "issuerName": "Advanced Safety Training", "expiry": "2026-01-01" } } }, "revocable": true, "isRevoked": false } ``` * `iss` : Use the Issuer DID. This must be a publicly available and resolvable `did:web` for the credential to be valid and verifiable. * `nbf` : Set the Unix epochtime and date the credential will be active from (Unix Epoch time) . * `exp` : Set the time and date the credential will expire (Unix Epoch time). * `vc` : This object includes all [custom claims](/docs/concepts/cwt/data) in the credential. These must comply with the [W3C Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model) and its vocabulary context. * `revocable` : When set to `true`, the signed credential can later be [revoked](/docs/issuance/revocation/overview). When set to `false`, the credential cannot be revoked. Defaults to `false`. * `isRevoked`: When set to `true`, the signed credential is issued as [revoked](/docs/issuance/revocation/overview), and must be unrevoked to become valid. If `isRevoked` is provided (e.g. set to either `true` or `false`), `revocable` must be set to `true`. Defaults to `false`. *Response* ```json title="Response body" { "id": "urn:uuid:78e19496-8521-424b-8315-35fb1ecaf681", // [!code highlight] "decoded": { "iss": "did:web:learn.vii.au01.mattr.global", "nbf": 1704099600, "exp": 1767258000, "vc": { "type": ["VerifiableCredential", "Course Credential"], // [!code highlight] "@context": ["https://www.w3.org/2018/credentials/v1"], // [!code highlight] "credentialSubject": { "name": "Emma Tasma", "code": "HS.278", "certificationName": "Working at Heights", "certificationLevel": "Level 4", "issuerName": "Advanced Safety Training", "expiry": "2026-01-01" } }, "status": { // [!code highlight] "index": 0, // [!code highlight] "url": "https://learn.vii.au01.mattr.global/v2/credentials/compact-semantic/revocation-lists/1fe00d6c-904f-497e-bbe1-a3cfdc0b8368" // [!code highlight] }, "jti": "urn:uuid:78e19496-8521-424b-8315-35fb1ecaf681" // [!code highlight] }, "encoded": "CSS:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSAOZUYAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQMJ3GHI3IIBRW63TUMV4HJALYEZUHI5DQOM5C6L3XO53S45ZTFZXXEZZPGIYDCOBPMNZGKZDFNZ2GSYLMOMXXMMLEOR4XAZMCORLGK4TJMZUWCYTMMVBXEZLEMVXHI2LBNRYUG33VOJZWKICDOJSWIZLOORUWC3DRMNZGKZDFNZ2GSYLMKN2WE2TFMN2KMZDOMFWWK2SFNVWWCICUMFZW2YLEMNXWIZLGJBJS4MRXHBYWGZLSORUWM2LDMF2GS33OJZQW2ZLSK5XXE23JNZTSAYLUEBEGK2LHNB2HG4TDMVZHI2LGNFRWC5DJN5XEYZLWMVWGOTDFOZSWYIBUNJUXG43VMVZE4YLNMV4BQQLEOZQW4Y3FMQQFGYLGMV2HSICUOJQWS3TJNZTWMZLYOBUXE6LKGIYDENRNGAYS2MBRAQNGSVRXSA5AAAIAACRAEAADPB7GQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UFVZWK3LBNZ2GSYZPOJSXM33DMF2GS33OFVWGS43UOMXTCZTFGAYGINTDFU4TANDGFU2DSN3FFVRGEZJRFVQTGY3GMRRTAYRYGM3DQB6YIBIHRYMUS2CSCQSLQMKTL6Y6ZL3ICWCAUPGIGEOOWODF77V7ZJPVLGAQC2ZUP7MASXIRTIRRPOIIBKNHKZ4LHROPWBPBCYTKA3GXWIRD736HIJNQENTSFUYIPQ77BG4ZPCTXYIY" // [!code highlight] } ``` * `id` : Unique credential identifier. This is required when [revoking a credential](/docs/issuance/revocation/overview). * `decoded` : This is the decoded version of the credential. It includes all the information provided in the request, with the addition of the following elements: * `type` : MATTR VII automatically injects `VerifiableCredential` for all Semantic CWT credentials. * `@context` : MATTR VII automatically injects `https://www.w3.org/2018/credentials/v1` for all Semantic CWT credentials. * `status` : If `revocable` was set to `true` in the request, this object is used to provide information required for revocation: * `index` : This indicates the index of this specific credential within the revocation list. * `url` : References the revocation List which holds the revocation status for this specific credential. * `jti` : This JWT ID identifies this credential and is identical to the `id` element. When `revocable` is set to `true`, this value is persisted on the tenant. * `encoded` : The base32 encoded string representation of the Semantic CWT credential. `CSS` stands for `COSE Sign Semantic` profile. You will use this element to convert the credential into a [format](#format-the-signed-cwt-credential) that can be shared with a holder. ### Format the signed CWT credential [#format-the-signed-cwt-credential] Once the CWT credential is signed, you can render it in one of several formats before sharing it. Make a request of the following structure to [format a CWT credential as a QR code](/docs/issuance/direct-issuance-api-reference/cwt-issuance#format-a-cwt-credential-as-a-qr-code): ```http title="Request" POST /v2/credentials/compact/qrcode ``` You can make a similar request to a different endpoint to [format a Semantic CWT credential as a QR code](/docs/issuance/direct-issuance-api-reference/semantic-cwt-issuance#format-a-semantic-cwt-credential-as-a-qr-code): ```http title="Request" POST /v2/credentials/compact-semantic/qrcode ``` ```json title="Request body" { "payload": "CSC:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSALWVQAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQHIAACAACOFBW65LSONSSAQ3SMVSGK3TUNFQWYBA2NFLDPEDENZQW2ZLPIVWW2YJAJJQW4ZJAKRQXG3LBMRRW6ZDFMZEFGLRSG44HCY3FOJ2GSZTJMNQXI2LPNZHGC3LFOJLW64TLNFXGOIDBOQQEQZLJM5UHI43SMNSXE5DJMZUWGYLUNFXW4TDFOZSWYZ2MMV3GK3BAGRVGS43TOVSXETTBNVSXQGCBMR3GC3TDMVSCAU3BMZSXI6JAKRZGC2LONFXGOZTFPBYGS4TZNIZDAMRWFUYDCLJQGE5AAAIAACRAEAYDPB2WQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UF5ZGK5TPMNQXI2LPNYWWY2LTORZS6OBYG5RWIMJUGAWWKNDEG4WTINJRHAWWENZQMYWTGMBVMIZDGNZXHA4DIOAH3BAFA3FHFPDIRRKK4SM3ZDTKQSRCPRCYIA7RFUZYQI3RIGDHIGLAODJ6K2F245DTLIIKXAD35TORFQ7MVRJCIEPH6SC6NGA4HRMK76H5V6GXP66FFNX7MNYC6MYVU7ZLLXYVLXBU", "width": 800 } ``` * `payload` : Use the `encoded` element from the response obtained when [signing the CWT credential](#sign-the-cwt-credential). * `width` : Optionally specify the desired width of the output QR code. When no width is specified MATTR VII will generate a QR code with an optimised width based on the size of the payload. Maximal value is 1000px. **Response** The response includes a QR code as a PNG file. This QR code contains the encoded credential and can be shared with the intended holder. **Errors** If the provided payload is invalid, the API will return one of the following errors: * `400` : * Payload is not a string. * Payload does not match a CWT credential format. * The credential has expired. * Unable to validate or decode the payload. * Generated QR code is larger than the provided width. * `413` : * The payload is too large to be stored in a QR code. Make a request of the following structure to [format a CWT credential as a PDF](/docs/issuance/direct-issuance-api-reference/cwt-issuance#format-a-cwt-credential-as-a-pdf): ```http title="Request" POST /v2/credentials/compact/pdf ``` You can make a similar request to a different endpoint to [format a Semantic CWT credential as a PDF](/docs/issuance/direct-issuance-api-reference/semantic-cwt-issuance#format-a-semantic-cwt-credential-as-a-pdf): ```http title="Request" POST /v2/credentials/compact-semantic/pdf ``` ```json title="Request body" { "templateId": "ccad3b98-7086-4556-9e19-9e2aa6ca5c5b", "payload": "CSC:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSALWVQAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQHIAACAACOFBW65LSONSSAQ3SMVSGK3TUNFQWYBA2NFLDPEDENZQW2ZLPIVWW2YJAJJQW4ZJAKRQXG3LBMRRW6ZDFMZEFGLRSG44HCY3FOJ2GSZTJMNQXI2LPNZHGC3LFOJLW64TLNFXGOIDBOQQEQZLJM5UHI43SMNSXE5DJMZUWGYLUNFXW4TDFOZSWYZ2MMV3GK3BAGRVGS43TOVSXETTBNVSXQGCBMR3GC3TDMVSCAU3BMZSXI6JAKRZGC2LONFXGOZTFPBYGS4TZNIZDAMRWFUYDCLJQGE5AAAIAACRAEAYDPB2WQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UF5ZGK5TPMNQXI2LPNYWWY2LTORZS6OBYG5RWIMJUGAWWKNDEG4WTINJRHAWWENZQMYWTGMBVMIZDGNZXHA4DIOAH3BAFA3FHFPDIRRKK4SM3ZDTKQSRCPRCYIA7RFUZYQI3RIGDHIGLAODJ6K2F245DTLIIKXAD35TORFQ7MVRJCIEPH6SC6NGA4HRMK76H5V6GXP66FFNX7MNYC6MYVU7ZLLXYVLXBU" } ``` * `templateId` : Use the `id` of the [PDF template](/docs/issuance/cwt-credential-templates/pdf-templates) you wish to use to format this credential. * `payload` : Use the `encoded` element from the response obtained when [signing the CWT credential](#sign-the-cwt-credential). **Response** The response includes a PDF document with the information from the credential formatted according to the PDF template. **Errors** Bad request (400) Errors may occur in the following scenarios: * Payload is not a string. * Payload does not match a CWT credential format. * The credential has expired. * Unable to validate or decode the payload. * The PDF template does not exist. * The decoded payload doesn't include a claim required for the PDF template. Your Apple developer certificates must be valid when attempting to format CWT credentials as Apple digital passes. Make a request of the following structure to [format a CWT credential as an Apple digital pass](/docs/issuance/direct-issuance-api-reference/cwt-issuance#format-a-cwt-credential-as-an-apple-pass): ```http title="Request" POST /v2/credentials/compact/digital-pass/apple ``` You can make a similar request to a different endpoint to [format a Semantic CWT credential as an Apple digital pass](/docs/issuance/direct-issuance-api-reference/semantic-cwt-issuance#format-a-semantic-cwt-credential-as-an-apple-pass): ```http title="Request" POST /v2/credentials/compact-semantic/digital-pass/apple ``` ```json title="Request body" { "templateId": "1b04f0ee-8e3e-4153-a0e0-8603a10e7f0a", "payload": "CSC:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSALWVQAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQHIAACAACOFBW65LSONSSAQ3SMVSGK3TUNFQWYBA2NFLDPEDENZQW2ZLPIVWW2YJAJJQW4ZJAKRQXG3LBMRRW6ZDFMZEFGLRSG44HCY3FOJ2GSZTJMNQXI2LPNZHGC3LFOJLW64TLNFXGOIDBOQQEQZLJM5UHI43SMNSXE5DJMZUWGYLUNFXW4TDFOZSWYZ2MMV3GK3BAGRVGS43TOVSXETTBNVSXQGCBMR3GC3TDMVSCAU3BMZSXI6JAKRZGC2LONFXGOZTFPBYGS4TZNIZDAMRWFUYDCLJQGE5AAAIAACRAEAYDPB2WQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UF5ZGK5TPMNQXI2LPNYWWY2LTORZS6OBYG5RWIMJUGAWWKNDEG4WTINJRHAWWENZQMYWTGMBVMIZDGNZXHA4DIOAH3BAFA3FHFPDIRRKK4SM3ZDTKQSRCPRCYIA7RFUZYQI3RIGDHIGLAODJ6K2F245DTLIIKXAD35TORFQ7MVRJCIEPH6SC6NGA4HRMK76H5V6GXP66FFNX7MNYC6MYVU7ZLLXYVLXBU" } ``` * `templateId` : Use the `id` of the [Apple digital pass template](/docs/issuance/cwt-credential-templates/apple-templates) you wish to use to format this credential. * `payload` : Use the `encoded` element from the response obtained when [signing the CWT credential](#sign-the-cwt-credential). **Response** The response includes a signed bundle `.pkpass` file (binary format). **Errors** Bad request (400) Errors may occur in the following scenarios: * Payload is not a string. * Payload does not match a CWT credential format. * The credential has expired. * Unable to validate or decode the payload. * Apple developer account credentials are missing or incorrect. * The template does not exist or is incorrectly defined. * Required `config.json` fields are not defined. * The file name contains a special character that is not supported by the Apple pass template endpoint. * The decoded payload is missing a required value for `pass.json`. Your Google developer certificates must be valid when attempting to format CWT credentials as Google digital passes. Make a request of the following structure to [format a CWT credential as a Google digital pass](/docs/issuance/direct-issuance-api-reference/cwt-issuance#format-a-cwt-credential-as-a-google-pass): ```http title="Request" POST /v2/credentials/compact/digital-pass/google ``` You can make a similar request to a different endpoint to [format a Semantic CWT credential as a Google digital pass](/docs/issuance/direct-issuance-api-reference/semantic-cwt-issuance#format-a-semantic-cwt-credential-as-a-google-pass): ```http title="Request" POST /v2/credentials/compact-semantic/digital-pass/google ``` ```json title="Request body" { "templateId": "1b04f0ee-8e3e-4153-a0e0-8603a10e7f0a", "payload": "CSC:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSALWVQAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQHIAACAACOFBW65LSONSSAQ3SMVSGK3TUNFQWYBA2NFLDPEDENZQW2ZLPIVWW2YJAJJQW4ZJAKRQXG3LBMRRW6ZDFMZEFGLRSG44HCY3FOJ2GSZTJMNQXI2LPNZHGC3LFOJLW64TLNFXGOIDBOQQEQZLJM5UHI43SMNSXE5DJMZUWGYLUNFXW4TDFOZSWYZ2MMV3GK3BAGRVGS43TOVSXETTBNVSXQGCBMR3GC3TDMVSCAU3BMZSXI6JAKRZGC2LONFXGOZTFPBYGS4TZNIZDAMRWFUYDCLJQGE5AAAIAACRAEAYDPB2WQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UF5ZGK5TPMNQXI2LPNYWWY2LTORZS6OBYG5RWIMJUGAWWKNDEG4WTINJRHAWWENZQMYWTGMBVMIZDGNZXHA4DIOAH3BAFA3FHFPDIRRKK4SM3ZDTKQSRCPRCYIA7RFUZYQI3RIGDHIGLAODJ6K2F245DTLIIKXAD35TORFQ7MVRJCIEPH6SC6NGA4HRMK76H5V6GXP66FFNX7MNYC6MYVU7ZLLXYVLXBU" } ``` * `templateId` : Use the `id` of the [Google digital pass template](/docs/issuance/cwt-credential-templates/google-templates) you wish to use to format this credential. * `payload` : Use the `encoded` element from the response obtained when [signing the CWT credential](#sign-the-cwt-credential). *Response* ```json title="Response body" { "redirectTo": "https://pay.google.com/gp/v/save/ejqEeFUtiFRF2t_0hXelmd1TDaeoPES091NT7LBiDvrmKpYPrOlhBfeSKOaA" } ``` * `redirectTo` : This is the URL where the digital pass is available to download. **Errors** Bad request (400) Errors may occur in the following scenarios: * Payload is not a string. * Payload does not match a CWT credential format. * The credential has expired. * Unable to validate or decode the payload. * Google account credentials are missing or incorrect * The template does not exist or is incorrectly defined. * The decoded payload doesn't include a field required in `template.json`. ### Share the formatted credential [#share-the-formatted-credential] Now that you have the credential rendered in the required format, you can share it with its intended holder. Share the QR code image using your preferred communication channel (e.g. e-mail, messaging platform, etc.). Share the PDF document using your preferred communication channel (e.g. e-mail, messaging platform, etc.). Share the `.pkpass` file using your preferred communication channel (e.g. e-mail, messaging platform, etc.). The recipient will be able to save the file into their Apple wallet. You can use the "Add to Apple Wallet" badge as a visual cue. Download files from [Add to Apple Wallet badge guidelines](https://developer.apple.com/wallet/add-to-apple-wallet-guidelines/). Add to Apple Wallet Share the download URL using your preferred communication channel (e.g. e-mail, messaging platform, etc.). The recipient can use the link to download the credential and save it into their Google wallet. You can use the "Add to Google Wallet" badge as a visual cue. Download files from [Google Wallet brand guidelines](https://developers.google.com/wallet/generic/resources/brand-guidelines). Add to Google
          Wallet # Define your credential structure URL: /docs/issuance/defining-credential-structure Before issuing, you need to define what data the credential will contain and how it maps from your source systems. This page covers the credential configuration template, how to map your data, and where claim data can come from. ## Define your credential structure [#define-your-credential-structure] Before issuing, you need to define what data the credential will contain and how it maps from your source systems. ### Credential configuration [#credential-configuration] A [credential configuration](/docs/issuance/credential-configuration/overview) is the template that defines: * **Credential type and format**: mDocs (ISO/IEC 18013-5) or CWT. * **Claim mappings**: How source data maps to credential attributes, organized by namespace. * **Validity rules**: `validFrom`, `validUntil`, or relative expiry (`expiresIn`). * **Revocation**: Whether to include status list references. * **Branding**: Display metadata for wallet rendering (name, logo, colors). ### Credential data mapping [#credential-data-mapping] Map your existing data models (e.g., from legacy digital credentials or other data sources) to the relevant ISO/IEC 18013-5 (or equivalent) schema. All claim mapping logic, including translation of custom or legacy fields, should occur in your [claims source](/docs/issuance/claims-source/overview) or be handled before passing data to the credential configuration. ### Claim data sources [#claim-data-sources] Claims can come from multiple sources depending on your workflow: | Source | Available in | Description | | ------------------------------------------------------ | ------------------- | ------------------------------------------------ | | Authentication provider (ID token) | Authorization Code | Claims from the user's OIDC authentication | | Interaction hook response | Authorization Code | Claims returned by your custom interaction step | | Credential offer payload | Pre-authorized Code | Claims supplied directly when creating the offer | | [Claims source](/docs/issuance/claims-source/overview) | Both flows | External API call to fetch additional data | ## Next steps [#next-steps] With your credential structure defined, [deliver credential offers to holders](/docs/issuance/delivering-offers). # Deliver credential offers to holders URL: /docs/issuance/delivering-offers The credential offer is what starts the issuance flow for the holder. This page covers how offers reach your users and which wallet applications can claim them. ## Deliver credential offers to holders [#deliver-credential-offers-to-holders] The credential offer is what starts the issuance flow for the holder. You need to decide how offers reach your users and which wallet applications can claim them. ### Offer delivery methods [#offer-delivery-methods] | Method | Best for | Description | | ----------- | ----------------------------------- | -------------------------------------------- | | QR code | In-person kiosks, printed materials | User scans with their wallet app | | Deep link | Email, SMS, in-app notifications | Clickable URL that opens the wallet | | Silent push | Existing app sessions | Credential delivered without user initiation | ### Wallet targeting [#wallet-targeting] You can control which wallet apps can claim your offers: * **Open model**: any OID4VCI-compatible wallet can claim using the standard `openid-credential-offer://` scheme. * **Private-use scheme**: a custom URI scheme targets specific wallet applications. * **Claimed HTTPS links**: App Links (Android) or Universal Links (iOS) provide the strongest app-binding with domain verification. * **[Wallet attestation](/docs/issuance/credential-issuance/wallet-attestation)**: requires wallets to cryptographically prove their authenticity before claiming credentials. This provides the strongest level of wallet trust by validating the wallet instance through a certificate-based trust chain, ensuring only authorized wallet applications can receive your credentials. See [claiming credential offers](/docs/issuance/credential-offer/overview#claiming-credential-offers) for detailed configuration guidance. ## Next steps [#next-steps] Extend the basic workflow with [optional components](/docs/issuance/optional-components) such as an authentication provider, claims source, or interaction hook. # Direct Credential issuance URL: /docs/issuance/direct-issuance-overview Direct issuance begins with making an API request to a MATTR VII endpoint, providing a credential payload. The endpoint responds with a cryptographically signed digital credential. MATTR VII currently supports direct issuance of [CWT credentials](/docs/concepts/cwt) and [Semantic CWT credentials](/docs/concepts/cwt). Once the credential is cryptographically signed, it can be shared with its intended holder via various communication channels, depending on the credential format and your implementation. **CWT and Semantic CWT credentials** can be [formatted](/docs/issuance/cwt-direct-issuance#format-the-signed-cwt-credential) as a QR code, PDF document, Apple or Google digital passes. Formatting is based on templates you create and upload to your MATTR VII tenant. Formatted credentials can be shared with holders via e-mail, messaging systems or even as paper-based credentials (for QR codes and PDFs). # Frequently asked questions URL: /docs/issuance/faq Answers to common questions about credential issuance with MATTR VII. For the concepts behind these answers, see the [issuance overview](/docs/issuance). ## Frequently asked questions [#frequently-asked-questions] ### What credential formats does MATTR VII support for issuance? [#what-credential-formats-does-mattr-vii-support-for-issuance] MATTR VII supports [mDocs](/docs/concepts/mdocs) (aligned with ISO/IEC 18013-5) and [CWT/Semantic CWT](/docs/concepts/cwt) credential formats through OID4VCI. Direct issuance (API-based, no wallet interaction) is available for CWT credentials. ### Which wallets can receive credentials issued by MATTR VII? [#which-wallets-can-receive-credentials-issued-by-mattr-vii] Any wallet that implements the [OID4VCI specification](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) can receive credentials. You can target specific wallets using custom URI schemes or claimed HTTPS links, or allow any compatible wallet using the standard scheme. MATTR also provides its own holding solutions that are fully compatible with MATTR VII issuance: * **[MATTR Holder SDKs](/docs/holding)**: embed credential holding capabilities directly into your own mobile application (iOS, Android, React Native). * **[MATTR GO](/docs/holding/go-hold/getting-started)**: a ready-to-use wallet application for end users. ### Do I need my own identity provider? [#do-i-need-my-own-identity-provider] For the Authorization Code flow, yes, you need an OIDC-compliant identity provider. For the Pre-authorized Code flow, no. You authenticate users through your own means before creating the credential offer. ### Can I issue credentials without user interaction? [#can-i-issue-credentials-without-user-interaction] Yes. The Pre-authorized Code flow supports silent issuance within existing app sessions. Your application creates the credential offer programmatically and the wallet claims it without requiring the user to authenticate again. ### How do I populate credential data from my existing systems? [#how-do-i-populate-credential-data-from-my-existing-systems] Use a [claims source](/docs/issuance/claims-source/overview), an HTTPS endpoint that MATTR VII calls to fetch data from your backend systems. Claims are mapped into the credential via your credential configuration's claim mappings. ### What happens if my claims source is unavailable? [#what-happens-if-my-claims-source-is-unavailable] If the claims source times out (>3 seconds) or returns a non-2xx response, issuance fails. Design your claims source for high availability and consider which claims are marked as `required` vs optional with `defaultValue` fallbacks. ### Can I add custom verification steps before issuing? [#can-i-add-custom-verification-steps-before-issuing] Yes. Use an [interaction hook](/docs/issuance/authorization-code/interaction-hook/overview) in the Authorization Code flow to add biometric checks, consent flows, or other custom logic between authentication and issuance. ### How do I revoke a credential after issuance? [#how-do-i-revoke-a-credential-after-issuance] Use the MATTR VII revocation API to update the credential's status. The updated status is reflected in the published status list that verifiers check. See [credential revocation](/docs/issuance/revocation/overview) for details. ### Can I issue multiple credentials in one flow? [#can-i-issue-multiple-credentials-in-one-flow] Yes. A credential offer can reference multiple credential configurations, allowing the holder to claim several credentials in a single interaction. ### What is the maximum validity period for an mDL? [#what-is-the-maximum-validity-period-for-an-mdl] mDL credentials have a maximum validity constraint of 427 days, enforced by the credential configuration. Plan your refresh or re-issuance strategy accordingly. # Credential Issuance URL: /docs/issuance Issuing verifiable credentials (such as mobile driver's licenses (mDLs), employee badges, health cards, or proof-of-age tokens) requires orchestrating identity verification, data sourcing, cryptographic signing, and secure delivery to a holder's wallet. As an implementer, you need to understand the components involved and how they fit together within your existing systems. This overview walks you through the key architectural decisions for building a credential issuance solution using [MATTR VII](/docs/digital-trust-service), from selecting your issuance workflow through to managing credential lifecycle. Start here, then follow the pages in order or jump to the topic you need. ## How credential issuance works [#how-credential-issuance-works] At its core, credential issuance involves three parties: 1. **Issuer**: Your organization, using MATTR VII to create and sign credentials. 2. **Holder**: The end user who receives and stores the credential in their wallet. 3. **Wallet**: An application that claims, stores, and later presents the credential. The issuance process follows the [OpenID for Verifiable Credential Issuance (OID4VCI)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) standard. Your application creates a credential offer, the holder's wallet resolves issuer metadata, authenticates (if required), and receives a cryptographically signed credential. The result is a credential that is: * **Tamper-evident**: Any modification invalidates the signature. * **Verifiable**: Any relying party can confirm authenticity against the issuer's certificate chain without contacting the issuer. * **Portable**: Stored in the holder's wallet and presentable across contexts. * **Revocable**: Status can be updated after issuance. ## Underlying platforms [#underlying-platforms] MATTR Credential Issuance capabilities All issuance capabilities are provided through MATTR VII tenants, accessible via the MATTR Portal or API. ## Explore the overview [#explore-the-overview] Work through these pages to design your issuance solution from simple to more complex concepts: 1. [Choose your issuance workflow](/docs/issuance/choosing-a-workflow): Pre-authorized Code versus Authorization Code. 2. [Define your credential structure](/docs/issuance/defining-credential-structure): credential configuration, data mapping, and claim data sources. 3. [Deliver credential offers to holders](/docs/issuance/delivering-offers): delivery methods and wallet targeting. 4. [Optional components](/docs/issuance/optional-components): authentication provider, claims source, and interaction hooks. 5. [Manage issuer keys and certificates](/docs/issuance/keys-and-certificates): key management options and the certificate chain of trust. 6. [Manage credential lifecycle](/docs/issuance/credential-lifecycle): revocation and monitoring with analytics. 7. [Frequently asked questions](/docs/issuance/faq): answers to common issuance questions. # Direct issuance journey pattern URL: /docs/issuance/issuance-journey-pattern-direct In this method you make a direct API request to sign a provided payload as a digital credential, and then share it with its intended holder. * **Issuance channel**: Remote, Unsupervised * **Device/s**: Cross-device * **Formats**: CWT * **Information assurance level**: High * **Identity assurance level**: Low or High (with accompanying identification) ## Journey flow [#journey-flow] Direct CWT issuance journey pattern 1 ### Receiving notification a credential is available [#receiving-notification-a-credential-is-available] Samantha receives an email confirming the issuance of a credential that she can claim. ### Unique access code [#unique-access-code] The email includes instructions along with a unique code that Samantha can use to claim a digital version of the credential into her digital wallet or device. ### Providing identifying information [#providing-identifying-information] Samantha navigates to the provided website and enters the digital access code printed in the email, along with a piece of information that isn’t provided in the instructions, that only she would know * like her date of birth. Direct CWT issuance journey pattern 2 ### Claiming the credential [#claiming-the-credential] Samantha is able to view the credential through the website and then select to save it as a: * QR code. * PDF document. * Digital pass in her Google Pay pass, Apple Wallet pass or any 3rd party wallet. ### Validating credential issuer [#validating-credential-issuer] When Samantha is prompted to accept the credential, her wallet is checking that this Issuer is allowed to issue this type of credential in her current trust network. This is an optional feature for network operators to increased the level of trust. ### Viewing the credential in wallet [#viewing-the-credential-in-wallet] After accepting the credential, it is available in Samantha’s digital wallet. ## Architecture [#architecture] Direct CWT issuance architecture ### Logging into the portal [#logging-into-the-portal] The Issuer’s portal (1) is the starting point. The portal could be accessed either directly by the user or some other form of digital journey that brings them to this step. Once authenticated in the portal, which could simply be through the entering of a link or piece of information, the Issuer can attempt to match the user to a record in the data store (2). The portal can then prompt the user to claim a credential. ### Credential generation [#credential-generation] The credential Issuer then retrieves and bundles the information relating to the requesting user from their own data source (2). The data is passed to the Credential Generation component (3) which formats and signs the data into a credential, ready to be shared with the user as a QR code, PDF or a digital pass. This step could include storing on a device/form of storage that doesn’t require the credential to be digitally bound to the user. ### Bearer credentials [#bearer-credentials] The only verifiable binding that can occur on this implementation pattern is between the credential and the Issuer, as there are no authentication or device binding attributes supplied to build into the credential. Binding the user to the information can only occur by comparing the claims in the credential to another trusted information source (for example, comparing the details in the credential with another verifiable credential bound to the user’s device). # Manage issuer keys and certificates URL: /docs/issuance/keys-and-certificates Credentials must be signed with keys your organization controls. This page covers your key management options and the certificate chain of trust that lets verifiers recognize your credentials. ## Manage issuer keys and certificates [#manage-issuer-keys-and-certificates] Credentials must be signed with keys your organization controls. The signing certificate chain establishes trust. Verifiers check that the credential was signed by a recognized issuer. ### Key management options [#key-management-options] | Option | Description | Use case | | ------------------------------------------------------------------- | ------------------------------------------- | ------------------------------- | | MATTR managed KMS (SSM) | Software-based key storage managed by MATTR | Default for most deployments | | MATTR managed KMS (HSM) | Hardware Security Module for key protection | High-assurance requirements | | [External KMS](/docs/concepts/chain-of-trust#external-certificates) | Bring your own key management solution | Organizations with existing PKI | Refer to the [PKI spec sheet](https://files.mattr.global/specsheets/mcc-pki.pdf) for detailed information on MATTR's key management capabilities. ### Certificate chain of trust [#certificate-chain-of-trust] For mDL issuance, your signing certificates must chain to an [Issuing Authority Certificate Authority (IACA)](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) root. This is the same root that verifiers check during presentation. See the [certificates overview](/docs/issuance/certificates/overview) and [chain of trust concepts](/docs/concepts/chain-of-trust) for detailed guidance. ## Next steps [#next-steps] Finally, learn how to [manage credential lifecycle](/docs/issuance/credential-lifecycle) after issuance. # OID4VCI URL: /docs/issuance/oid4vci-overview Description: A plain-language guide to OID4VCI (OpenID for Verifiable Credential Issuance) - what it is, how it works, how to connect a system of record to a credential issuance flow, and how MATTR VII and the MATTR Portal support it. ## What is OID4VCI? [#what-is-oid4vci] **OID4VCI** stands for **OpenID for Verifiable Credential Issuance**. It is an open standard published by the OpenID Foundation that defines how a credential issuer (such as a government department, financial institution, or employer) can deliver verifiable credentials to a digital wallet in a secure and interoperable way. OID4VCI builds on **OAuth 2.0** and **OpenID Connect**, two protocols already widely used across the web for authentication and authorization. Reusing those foundations means issuers, wallets, and identity providers can adopt OID4VCI without reinventing core security plumbing. The full specification is available at the [OpenID Foundation](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html). If you are unfamiliar with OpenID Connect, the identity protocol underpinning the OpenID provisioning capability, there are many excellent guides available online such as this guide from [Google](https://developers.google.com/identity/openid-connect/openid-connect), or this guide from [Mozilla](https://infosec.mozilla.org/guidelines/iam/openid_connect.html). ## Why OID4VCI matters [#why-oid4vci-matters] Before OID4VCI, an issuer wanting to deliver a digital credential to a wallet often had to build a bespoke integration with each wallet. That model does not scale. Holders end up with a fragmented experience, issuers carry the cost of multiple integrations, and the ecosystem grows slowly because every new participant has to coordinate with every other. OID4VCI changes the model. Any wallet that implements the standard can receive credentials from any issuer that implements it, regardless of vendor. That unlocks: * **Interoperability**: A single issuance integration that works across many wallets. * **Choice for holders**: Holders pick the wallet that suits them, and any compliant wallet can hold the credential. * **Lower integration cost**: Issuers do not need to maintain a separate code path per wallet provider. * **Standards alignment**: OID4VCI aligns with regulatory and ecosystem direction, such as the European Digital Identity Wallet (EUDI) framework. ## How OID4VCI works at a high level [#how-oid4vci-works-at-a-high-level] At a glance, OID4VCI follows three steps: 1. **Offer**: The issuer prepares an offer, which tells the wallet what credentials are available and how to claim them. 2. **Authorize**: The wallet (and where relevant, the holder) authorizes the credential request. The exact mechanism depends on which OID4VCI flow is used. 3. **Issue**: The wallet receives an access token, requests the credential, and the issuer responds with a signed verifiable credential. The specification accommodates different real-world scenarios through two distinct flows. ## OID4VCI flows [#oid4vci-flows] OID4VCI defines two distinct workflows, each tailored to different use cases and requirements: * [Authorization Code flow](/docs/issuance/authorization-code/overview): This interactive, user-driven flow requires the credential recipient (typically a wallet) to redirect the user to the issuer (such as a government or organization) for authentication. After the user successfully authenticates and gives consent, the issuer's authentication provider returns an authorization code. The wallet then exchanges this code for an access token, which is used to obtain the credential. The following credential formats can be issued via the Authorization Code flow: * [CWT](/docs/concepts/cwt) * [Semantic CWT](/docs/concepts/cwt) * [mDocs](/docs/concepts/mdocs) * [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview): In this flow, the issuer prepares the credential issuance in advance and may authenticate and authorize the holder ahead of time. Instead of obtaining an authorization code through user authentication, the wallet receives a pre-authorized code directly from the issuer, often via an out-of-band method. The user does not need to authenticate again and the wallet presents the pre-authorized code to retrieve an access token and then claim the credential. For added security, the issuer can require a transaction code (shared separately with the holder) which the wallet must also provide to claim the credential. The Pre-authorized Code flow is only supported for [mDocs](/docs/concepts/mdocs). MATTR VII supports both workflows, allowing you to choose the one that best fits your use case. ## Connecting a system of record to an issuance flow [#connecting-a-system-of-record-to-an-issuance-flow] Most organizations already hold the data they want to issue as a credential in an existing system: a customer database, an HR system, a license registry, a learning record store. OID4VCI does not replace that system. Instead, MATTR VII connects to it at the moment of issuance and uses the existing data to populate the credential. In MATTR VII, this connection is modeled as a **claims source**. A claims source defines: * Where the source data lives (an HTTP endpoint, for example). * How MATTR VII authenticates to that endpoint. * How the response is mapped onto the claims of the credential being issued. A typical end-to-end issuance setup looks like this: 1. **Define the credential**: Create a credential configuration that describes the credential format, the claims it carries, and how it is signed. 2. **Connect the system of record**: Configure a [claims source](/docs/issuance/claims-source/overview) so MATTR VII knows where to fetch the holder's data at issuance time. 3. **Choose an issuance flow**: Decide between the [Authorization Code flow](/docs/issuance/authorization-code/overview) and the [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview), based on whether the holder needs to authenticate interactively. 4. **Deliver the offer**: Generate a credential offer and deliver it to the holder, for example via a QR code, deep link, or in-app prompt. 5. **Issue the credential**: When the holder accepts the offer in their wallet, MATTR VII retrieves the relevant data from the claims source, assembles and signs the credential, and delivers it through OID4VCI. Once issued, the credential lives in the holder's wallet and can be presented to verifiers without going back to the issuer. ## How to get started with OID4VCI on MATTR [#how-to-get-started-with-oid4vci-on-mattr] You can build and operate an OID4VCI issuance flow with MATTR through two complementary surfaces: * **[MATTR Portal](https://portal.mattr.global)**: A web interface for managing your MATTR VII tenants. The Portal lets you create credential configurations, manage claims sources, configure issuance flows, and monitor activity without writing code. It is the fastest way to stand up an end-to-end issuance flow for prototyping or production use. Learn more in the [MATTR Portal documentation](/docs/platform-management/portal). * **[MATTR VII API](/docs/api-reference)**: A REST API for full programmatic control over every aspect of issuance, including credential configurations, claims sources, offers, and webhook events. Use the API when you need to embed issuance into your own systems or automate large-scale operations. For a guided walkthrough, see the [Credential Issuance overview](/docs/issuance). If you are still deciding which approach fits your use case, or would like a deeper conversation about your issuance design, [contact us](mailto:dev-support@mattr.global). ## Frequently asked questions [#frequently-asked-questions] ### What is OID4VCI? [#what-is-oid4vci-1] **OID4VCI (OpenID for Verifiable Credential Issuance)** is an open standard from the OpenID Foundation that defines how a digital credential issuer can deliver verifiable credentials to a digital wallet in a secure and interoperable way. It builds on **OAuth 2.0** and **OpenID Connect**, two protocols already widely used for authentication and authorization on the web. ### Why does OID4VCI matter for credential issuance? [#why-does-oid4vci-matter-for-credential-issuance] OID4VCI gives issuers a standards-based, interoperable way to deliver verifiable credentials to any compliant wallet, instead of building a bespoke integration per wallet. That means issuers can reach a broader holder base, wallets can support credentials from many issuers, and ecosystems can grow without each participant having to coordinate one-to-one. ### What are the two OID4VCI flows? [#what-are-the-two-oid4vci-flows] OID4VCI defines two flows: * **Authorization Code flow**: The holder is redirected to the issuer to authenticate and consent. The wallet then exchanges an authorization code for an access token and retrieves the credential. * **Pre-authorized Code flow**: The issuer prepares the credential in advance and hands the wallet a pre-authorized code (often via a QR code or link), so the holder does not need to authenticate again at issuance time. A transaction code can be added for additional security. ### How do I connect a system of record to an OID4VCI issuance flow? [#how-do-i-connect-a-system-of-record-to-an-oid4vci-issuance-flow] A system of record (the database or application that already holds the user's data) is connected to MATTR VII through a **claims source**. The claims source tells MATTR VII how to retrieve the data it needs to populate the credential at the moment of issuance. From the holder's perspective, they trigger the flow, authenticate (if required), and receive the credential into their wallet. Behind the scenes, MATTR VII calls the claims source, assembles the credential, signs it, and delivers it through OID4VCI. ### How do MATTR VII and the MATTR Portal support OID4VCI? [#how-do-mattr-vii-and-the-mattr-portal-support-oid4vci] MATTR VII supports both OID4VCI flows (Authorization Code and Pre-authorized Code) and multiple credential formats. You can configure issuance through the **MATTR VII Platform API**, or through the **MATTR Portal**, which provides a user interface for managing tenants, credential configurations, claims sources, and issuance flows without writing code. ### Which credential formats can MATTR VII issue via OID4VCI? [#which-credential-formats-can-mattr-vii-issue-via-oid4vci] Via the **Authorization Code flow**, MATTR VII can issue **CWT**, **Semantic CWT**, and **mDoc** credentials. Via the **Pre-authorized Code flow**, MATTR VII issues **mDoc** credentials. ### Is OID4VCI the same as OpenID Connect? [#is-oid4vci-the-same-as-openid-connect] No. **OpenID Connect** is a protocol for identity authentication, typically used to sign a user into an application. **OID4VCI** builds on the same underlying OAuth 2.0 foundations as OpenID Connect, but it is purpose-built for issuing verifiable credentials, not for authenticating sessions. The two are complementary and often used together (for example, OpenID Connect can authenticate the holder during the Authorization Code flow). ## Summary [#summary] OID4VCI is the open standard that lets credential issuers deliver verifiable credentials to wallets in a way that works across the ecosystem. It defines two flows (Authorization Code and Pre-authorized Code) to suit different use cases, and connects to your existing systems of record through a claims source. MATTR VII supports OID4VCI out of the box, with both the [MATTR Portal](https://portal.mattr.global) and the [MATTR VII API](/docs/api-reference) available to configure and operate your issuance flows. # Optional components URL: /docs/issuance/optional-components These components extend the basic issuance workflow with additional capabilities. Add them only when your assurance or data requirements call for them. ## Optional components [#optional-components] These components extend the basic issuance workflow with additional capabilities. ### Authentication provider [#authentication-provider] An [authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) is your existing OIDC-compliant identity provider (e.g., Auth0, Entra ID, Okta). It authenticates the holder during the Authorization Code flow. **Requirements:** * Must support OIDC Discovery (`.well-known/openid-configuration`). * Must support the authorization code grant. * Must support the `state` parameter. * One authentication provider per MATTR VII tenant. **How it fits in the workflow:** 1. Holder initiates credential claim. 2. MATTR VII redirects to your identity provider. 3. Holder authenticates and consents. 4. ID token claims become available for credential mapping. Ensure the claims your credential configuration needs are available from your identity provider. Check the `claims_supported` and `scopes_supported` metadata at your provider's discovery endpoint. See [ensuring claim availability](/docs/issuance/authorization-code/authentication-provider/overview#ensuring-claim-availability). ### Claims source [#claims-source] A [claims source](/docs/issuance/claims-source/overview) is an external HTTPS endpoint that MATTR VII calls during issuance to fetch additional claim data. This is useful when credential data lives in systems separate from your identity provider. **Common use cases:** * Government databases (license records, registry data). * HR systems (employee attributes, role assignments). * CRM or customer databases (membership tiers, entitlements). **How it fits in the workflow:** 1. Holder authenticates (Authorization Code) or presents offer (Pre-authorized Code). 2. MATTR VII calls your claims source endpoint with configured parameters. 3. Your endpoint returns JSON claim data. 4. Claims are mapped into the credential via the credential configuration. **Integration requirements:** * HTTPS endpoint reachable from MATTR VII (IPv4). * Must respond within 3 seconds. * Supports API key or OAuth client credentials for authorization. * Must return claims as a flat or nested JSON object. ### Interaction hooks [#interaction-hooks] An [interaction hook](/docs/issuance/authorization-code/interaction-hook/overview) adds a custom step between authentication and credential issuance in the Authorization Code flow. This lets you inject additional verification, consent collection, or data gathering. **Common use cases:** * Biometric or liveness verification. * Additional consent or terms acceptance. * Document upload or manual review steps. * Custom data collection forms. **How it fits in the workflow:** 1. Holder authenticates via the authentication provider. 2. MATTR VII redirects to your interaction hook URL with a signed session token. 3. Your component performs its logic (verification, consent, data collection). 4. Your component redirects back to MATTR VII with a signed response JWT containing any claims. 5. Returned claims become available for credential mapping. Interaction hooks add latency and complexity. Use them only when the Authorization Code flow's built-in authentication is insufficient for your assurance requirements. ## Next steps [#next-steps] Next, [manage issuer keys and certificates](/docs/issuance/keys-and-certificates) so your credentials are signed with keys your organization controls. # Privacy in credential issuance URL: /docs/issuance/privacy Description: How MATTR VII handles claims, user data, and credential metadata during issuance, and what configuration choices control the data footprint of an issuance flow. MATTR VII is the platform component that issues verifiable credentials. This page describes how MATTR VII handles personal data during issuance, what is retained after a credential is delivered, and the configuration choices that determine the data footprint of an issuance flow. For the broader picture of MATTR's privacy stance, see [Privacy in MATTR's architecture](/docs/concepts/privacy). ## Issuance is a transit operation [#issuance-is-a-transit-operation] MATTR VII is designed as a transit platform for credential issuance. The role of the platform is to take inputs from authoritative systems, produce a cryptographically signed credential, and deliver it to the holder's wallet. It is not designed as a long-term store of credential content. In practice, this means: * Claims are fetched at issuance time from the configured [Claims source](/docs/issuance/claims-source/overview) or supplied via the credential offer. * The claims are used to populate the credential and to compute the cryptographic signature. * The signed credential is delivered to the holder. * The claim values are not persisted by default. The issuer's system of record remains the authoritative source for the underlying data. MATTR VII does not become a parallel store of credential content. ## What MATTR VII retains by default [#what-mattr-vii-retains-by-default] To support audit, billing, and lifecycle operations such as revocation, MATTR VII retains a limited set of metadata for each credential it issues. By default, this includes: * A reference to the [User](/docs/issuance/users/overview) the credential was issued to. * The [credential configuration](/docs/issuance/credential-configuration/overview) used. * A hash of the issued credential (for mDocs, a hash of the MSO). * The device public key bound to the credential. * Issuance timestamps and status. This metadata does not include the actual claim values inside the credential. It is enough to support credential lifecycle operations without recreating the data minimization properties the architecture is designed to provide. If the customer wants to track which users have been issued which credentials, the [User](/docs/issuance/users/overview) object is the intended hook. The claims map on a user is deliberately small and is intended for pseudonymous identifiers (for example, an external user ID that maps back to the customer's system of record), not for storing the credential content itself. The contents of the `claims` object on a User are persistent. Avoid including sensitive personal information here. Best practice is to store a stable external identifier and look up any other data in the customer's system of record when needed. ## Claims sources [#claims-sources] A [Claims source](/docs/issuance/claims-source/overview) is the customer-controlled endpoint that MATTR VII calls during issuance to retrieve the data that will go into a credential. The platform acts purely as a client of that endpoint: * Requests are made over HTTPS using either an API key or OAuth client credentials. * MATTR VII passes only the request parameters configured by the customer, which can include identifiers from the authentication flow or the credential offer. * The response is used to populate the credential being issued. * The response is not retained beyond what is necessary to complete the issuance request. The customer controls what data their claims source returns. The privacy principle for this integration is the same one that applies elsewhere: return only the data needed for the credential being issued, not the full record about the user. ## OID4VCI flows and user data [#oid4vci-flows-and-user-data] The OID4VCI flows used by MATTR VII have different user-data implications. The [Authorization Code flow](/docs/issuance/authorization-code/overview) involves authenticating the user against the customer's authentication provider before the credential is issued. MATTR VII stores the relationship between the MATTR VII user and the authentication provider subject (so that future credentials can be associated with the same user), but it does not store the authentication provider's identity attributes. The [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview) does not involve a direct authentication interaction. The customer's backend has already established the user's identity before generating the credential offer. The flow can pass a pre-authorized code and an optional transaction code to bind the offer to a specific holder. In both flows, the data minimization principle is the same: pass only what is needed to identify the user for the issuance interaction. Anything else belongs in the customer's system of record. ## After issuance [#after-issuance] After a credential is delivered to the holder's wallet, MATTR VII's involvement in the credential ends, except for lifecycle operations the customer initiates: * **Revocation** is supported by publishing [status lists](/docs/issuance/revocation/overview). Status lists contain only an index and a binary revocation value. They do not contain personal information. * **Update** flows allow the holder's wallet to obtain an updated credential when one is available. The update interaction follows the same data-handling rules as initial issuance. MATTR VII does not observe where the credential is presented, by whom it is verified, or what specific attributes are disclosed. There is no central log of credential usage at the issuer. ## Practical guidance for issuers [#practical-guidance-for-issuers] * Use [Claims sources](/docs/issuance/claims-source/overview) to fetch data at issuance time rather than uploading bulk datasets to the platform. * Keep User `claims` to a pseudonymous external identifier where possible. Do not persist sensitive personal information on the User object. * Choose the regional cloud deployment that matches your data residency requirements at onboarding. Customer data stays within the chosen AWS region except for the listed sub-processors. * Make sure your end-user privacy notice reflects what your issuance flow actually collects and retains, and obtain consent from the user for anything you intend to persist. The architecture supports a minimal footprint, and the customer-facing notice should describe what the deployment actually does. * Handle data subject requests through the [Users API](/docs/issuance/users/api-reference). The [Get user](/docs/issuance/users/api-reference#retrieve-a-user) and [Get user credentials](/docs/issuance/users/api-reference#retrieve-user-credentials-data) endpoints return what MATTR VII holds about a given subject, and [Delete user](/docs/issuance/users/api-reference#delete-a-user) removes that record to support right-to-be-forgotten requests. Note that credentials already issued to the user remain valid after the User record is deleted. If a right-to-be-forgotten request requires invalidating those credentials, revoke them via the [status list](/docs/issuance/revocation/overview) as a separate step. * For dedicated cloud customers, configure analytics at Level 1 or Level 2. Level 3 (full event data) is only appropriate for short-lived debugging and should be paired with a reduced retention period. See the platform events registry for the available levels. ## Frequently asked questions [#frequently-asked-questions] ### Does MATTR VII store the claims contained in credentials it issues? [#does-mattr-vii-store-the-claims-contained-in-credentials-it-issues] No, not by default. MATTR VII operates as a transit platform during issuance. Claims flow through the platform to be signed into a credential and are then delivered to the holder. Claim values are not persisted unless the customer has explicitly configured them to be. ### Can MATTR VII track how a credential is used after it is issued? [#can-mattr-vii-track-how-a-credential-is-used-after-it-is-issued] No. Once a credential is delivered to the holder's wallet, MATTR VII has no visibility into where, when, or how it is later presented. The decentralized architecture makes this technically impossible from the issuer side. ### What information about issued credentials is retained? [#what-information-about-issued-credentials-is-retained] Limited audit information is retained, such as a hash of the credential, the credential configuration used, and a reference to the user the credential was issued to. The actual claim values are not retained by default. ### How are claims sources protected? [#how-are-claims-sources-protected] Claims sources are called over HTTPS using either an API key or OAuth client credentials. MATTR VII fetches only the data required for the specific credential being issued, uses it to populate the credential, and does not persist the response beyond what is needed to complete issuance. ### What if I need to retain claims for business reasons? [#what-if-i-need-to-retain-claims-for-business-reasons] Selected attributes can be configured to be persisted on the MATTR VII user via the claims source configuration. This is an explicit, opt-in choice. Best practice is to avoid persisting sensitive personal information and to favor pseudonymous external identifiers over full identity attributes. ## Related reading [#related-reading] * [Privacy in MATTR's architecture](/docs/concepts/privacy) * [Selective disclosure](/docs/concepts/selective-disclosure) * [Decentralized trust model](/docs/concepts/decentralized-trust-model) * [Claims source](/docs/issuance/claims-source/overview) * [Users](/docs/issuance/users/overview) * [Revocation](/docs/issuance/revocation/overview) # Learn how to setup role based access control for your MATTR VII tenant URL: /docs/platform-management/access-control-tutorial ## Overview [#overview] MATTR VII uses Role-Based Access Control (RBAC) to manage permissions and access within a tenant. Each role grants access to specific capabilities, ensuring that users or clients only have access to the functionalities they need. Refer to [Access control](/docs/platform-management/access-control) for more information. In this tutorial you will learn how to manage access control for your tenants by assigning roles to clients, either using the MATTR Portal or the Management API. This tutorial covers managing access controls for **clients**. If you want to manage access for users, see the [Inviting a user to a tenant](/docs/platform-management/portal#inviting-users) guide. That guide also explains how to assign roles to users. Once a user logs into the Portal and selects a tenant, they will only have access to the capabilities associated with their assigned roles. ## Prerequisites [#prerequisites] * You will need access to the MATTR Portal or client credentials (`client_id` and `client_secret`) to use the Management API. ## Tutorial steps [#tutorial-steps] 1. [Create a new tenant](#create-a-new-tenant): This tenant will be used to trial the RBAC configuration. 2. [Create a new client](#create-a-new-client): We will create a new client for our new tenant, and assign a specific set of permissions to the client. 3. [Test client permissions](#test-client-permissions): Finally we will use the new client credentials to obtain an access token and make requests to different endpoints. This should ensure the client permissions were properly configured. ### Create a new tenant [#create-a-new-tenant] Start off by creating a new tenant, either using the MATTR Portal or the Management API. 1. Select the **Create/switch tenant** button on the top-right side of the screen.\ The *All tenants* panel is displayed, listing any existing tenants. 2. Select the **Create new** button.\ The *New tenant* form is displayed. 3. Use the *Region* dropdown list to select the region your tenant will be hosted in. 4. Use the *Tenant subdomain* text box to insert a subdomain for your tenant (e.g. `access-control-tenant`). 5. Use the *Tenant name* text box to insert a meaningful and friendly name for your tenant (e.g. *Access Control Tutorial Tenant*). 6. Select the **Create** button to create the new tenant. 7. Copy the displayed tenant information and record it somewhere safe for future reference. 1. Make a request of the following structure to [obtain a Management API access token](/docs/api-reference/management/security/authToken): ```http title="Request" POST https://auth.manage.au01.mattr.global/oauth/token ``` ```json title="Request Body" { "client_id": "F5qae*************************", "client_secret": "Wzc8J**********************************************************", "audience": "https://manage.au01.mattr.global", "grant_type": "client_credentials" } ``` * `client_id` : Replace with the `client_id` value from your Management API client credentials. * `client_secret` : Replace with the `client_secret` value from your Management API client credentials. * `audience` : Use `https://manage.au01.mattr.global` as per the example above. * `grant_type` : Use `client_credentials` as per the example above. These are not the same `client_id` and `client_secret` you were provided for accessing the MATTR VII Platform API, but rather unique credentials for accessing the Management API. If you have not received these credentials or have any questions, please [contact us](mailto:dev-support@mattr.global) before proceeding. *Response* ```json title="Response body" { "access_token": "eyJhb********************************************************************", // [!code focus] "expires_in": 14400, "token_type": "Bearer" } ``` 2. Use the returned `access_token` must be used as a bearer token for all requests to the Management API in the next steps to make a request of the following structure to [create a new tenant](/docs/api-reference/management/tenants/createTenant): ```http title="Request" POST https://manage.au01.mattr.global/v1/tenants ``` ```json title="Request body" { "name": "Access Control Tutorial Tenant", "subdomain": "access-control-tenant", "environmentId": "fa605282-0223-4ae0-831d-af368bc39a55" } ``` * `name` : The name that will be used to identify this tenant. * `subdomain` : The subdomain that will be used to access this tenant. * `environmentId` : The unique identifier of the environment where the new tenant will be created. You can make a request to [retrieve all environments](/docs/api-reference/management/environments/getEnvironments) you have access to and use the `id` value from the response as the `environmentId`. *Response* ```json title="Response body" { "id": "6facbcef-66cd-4a06-89e3-e44a4fc12000", // [!code highlight] "name": "Access Control Tutorial Tenant", // [!code highlight] "subdomain": "access-control-tenant.vii.au01.mattr.global", // [!code highlight] "environment": { // [!code highlight] "id": "fa605282-0223-4ae0-831d-af368bc39a55", "name": "Public Australia Sydney", "domain": "vii.a01.mattr.global", "deploymentModel": "public", "authorizationServerDomain": "auth.manage.au01.mattr.global", "region": { "id": "0fd6ce12-a983-41d0-aca8-03e1bb6f6000", "name": "au01", "displayName": "Sydney, Australia" } }, "client": { // [!code highlight] "clientId": "MjQx108p***************FlwJQjy", "clientSecret": "NanfSkVr**********************PfD3zJ" } } ``` * `id` : Globally unique tenant identifier. * `name` : As provided in the request. * `subdomain` : The tenant URL, constructed with the `subdomain` value provided in the request. * `environment` : Indicates data for the environment in which the new tenant was created. * `client` : Indicates the `clientId` and `clientSecret` for the default client created for this tenant. This client is assigned an [Admin](/docs/platform-management/access-control#tenant-admin-permissions) role by default, meaning it has access to all endpoints in the tenant. ### Create a new client [#create-a-new-client] We now have a tenant and a default admin client. Next we will create a client that will have an Issuer role, enabling them to only access a subset of the tenant endpoints and capabilities. 1. Open **Platform Management** in the left navigation panel and select **Tenant**.\ The tenant management screen appears. 2. Click **Create/Switch Tenant** at the top-right.\ The All Tenants window opens. 3. Click **Switch** next to the tenant created in the previous step. 4. Under **Platform Management**, select **Users, clients & roles**. 5. Go to the **Clients** tab and click **Create new**. 6. Enter a name to identify the client in the *Name* field (e.g. *Tutorial Issuer client*). 7. Select the *Issuer* checkbox in the *Tenant access* section. 8. Click **Create** to create the client 9. Make note of the displayed client credentials (`auth_url`, `tenant_url`, `client_id` and `client_secret`) as you will need them in the next step. The `client_secret` is only displayed immediately after the client is created. Once you navigate away from this screen, the client secret will be masked and cannot be retrieved again. Ensure you save it securely at this point. Make a request of the following structure to [create a new tenant client](/docs/api-reference/management/clients/createTenantClient): ```http title="Request" POST https://manage.au01.mattr.global/v1/tenants/{tenantId}/clients ``` * `tenantId` : Replace with the `id` identifying the tenant, obtained from the previous step's response. This creates the client in the context of a specific tenant. ```json title="Request Body" { "name": "Tutorial Issuer client", "roles": ["issuer"] } ``` * `name` : Name of the client associated with this tenant. * `roles` : An array of [roles](/docs/platform-management/access-control#role-permissions) assigned to this client based on the capabilities it needs to access. In our example we are assigning it the role of an [Issuer](/docs/platform-management/access-control#issuer-permissions). *Response* ```json title="Response body" { "clientId": "suC7I*******************************", "clientSecret": "Qn_43J****************************************************", "name": "Example client", "permissions": ["permission_1", "permission_2", "permission_3"], "roles": ["issuer"] } ``` * `clientId` : Client identifier for retrieving a tenant access token. * `clientSecret` : Client secret for retrieving a tenant access token. * `name` : As provided in the request. * `permissions` : An array of permissions assigned to the client based on the defined `roles`. * `roles` : As provided in the request. ### Test client permissions [#test-client-permissions] You have successfully created a new client with a limited set of [permissions](/docs/platform-management/access-control#issuer-permissions) relevant to credential issuance. We can now test these permissions work as expected. Regardless of whether you created the client using the MATTR Portal or the Management API, you should have the `clientId` and `clientSecret` values available to you. You will need these to obtain an access token for the new client. **Step 1: Obtain access token** Use the new client's `clientId` and `clientSecret` obtained in the previous step to obtain an access token for accessing the tenant: ```http title="Request" POST https://auth.au01.mattr.global/oauth/token/oauth/token ``` If you created your tenant in a different environment you might need to use a different authentication server. Please [contact us](mailto:dev-support@mattr.global) if you are unsure. ```json title="Request body" { "client_id": "F5qae*******************************", "client_secret": "Wzc8J**********************************************************", "audience": "access-control-tenant.vii.au01.mattr.global", "grant_type": "client_credentials" } ``` * `client_id` : Replace with the `clientId` value obtained in the previous step. * `client_secret` : Replace with the `clientSecret` value obtained in the previous step. * `audience` : Replace with the `subdomain` obtained when you created the tenant. * `grant_type` : Use `client_credentials` as per the example above. *Response* ```json title="Response body" { "access_token": "eyJhb********************************************************************", // [!code focus] "expires_in": 14400, "token_type": "Bearer" } ``` The returned `access_token` will enable accessing endpoints as per the `Issuer` role assigned to the client. Let's test this works as expected. **Step 2: Make a request to an endpoint the client should have access to** Use the `access_token` obtained in the previous step and make a request of the following structure to [create a new IACA](/docs/issuance/certificates/api-reference/iaca#create-an-iaca): ```http filename:"Request" POST https://access-control-tenant.vii.au01.mattr.global/v2/credentials/mobile/iacas ``` ```json filename:"Request body" { "commonName": "Example IACA", "country": "US", "stateOrProvinceName": "US-AL", "notBefore": "2024-09-26", "notAfter": "2034-09-26" } ``` As a client with an `Issuer` role is expected to have access to this endpoint (used to create new [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) certificates), you should receive a successful `201` response. **Step 3: Make a request to an endpoint the client should not have access to** Use the `access_token` obtained in the previous step and make a request of the following structure to [create a new Ecosystem](/docs/issuance/certificates/api-reference/iaca#create-an-iaca): ```http title="Request" POST https://access-control-tenant.vii.au01.mattr.global/v1/ecosystems ``` ```json title="Request body" { "name": "My Ecosystem" } ``` As a client with an `Issuer` role is not expected to have access to this endpoint (used to create new [Ecosystems](/docs/digital-trust-service)), you should receive a `403` (Forbidden) error. ## Summary [#summary] In this tutorial you've learnt how to manage access control for your tenants by assigning roles to clients, either using the MATTR Portal or the Management API. You can now use a similar approach to assign different roles to different tenant clients as per your implementation requirements and security practices. # Access control URL: /docs/platform-management/access-control ## Overview [#overview] MATTR VII uses **Role-Based Access Control (RBAC)** to manage permissions and access within a tenant. Each role grants access to specific capabilities, ensuring that users or clients only have access to the functionalities they need. Below is a list of available roles and their descriptions: * [Tenant admin](#tenant-admin-permissions) (`admin`): Has full access to all tenant capabilities. This role is assigned to the default client when a new tenant is created. * [Issuer](#issuer-permissions) (`issuer`): Has access to capabilities required for issuing and managing credentials of different formats across different channels. * [Managed Issuer](#managed-issuer-permissions) (`managed-issuer`): Has access to capabilities required for issuing and managing credentials within a managed issuance setup. The main difference between this role and the *Issuer* role is that users or clients with the *Managed Issuer* role cannot create new IACAs or manage Credential configurations. * [Verifier](#verifier-permissions) (`verifier`): Has access to capabilities required for verifying credentials of different formats across different channels. * [DTS provider](#dts-provider-permissions) (`dts-provider`): Has access to capabilities required for managing a Digital Trust Service (DTS). * [DTS consumer](#dts-consumer-permissions) (`dts-consumer`): Has access to capabilities required to consume DTS information from a tenant. * [Auditor](#auditor-permissions) (`auditor`): Has read-only access to analytics data. ## Role permissions [#role-permissions] The following sections detail the capabilities available for different roles. For an inclusive list of the endpoints and operations each role can access, please refer to the [roles and permissions list](/docs/platform-management/roles-and-permissions). Furthermore, the MATTR VII [API reference](/docs/api-reference) details, for each endpoint, what roles can access it. ### Tenant admin permissions [#tenant-admin-permissions] The following list details the MATTR VII capabilities available to users and clients assigned with the *Tenant admin* role. This includes all tenant capabilities: **Platform management** * Manage [Custom domain](/docs/platform-management/custom-domain-overview) * Manage [DIDs](/docs/concepts/dids)\* * Manage [IACAs](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) * Manage [Webhooks](/docs/platform-management/webhooks-overview) * Monitor [Analytics](/docs/platform-management/analytics/overview) information * Manage Messaging\* * Manage [Tenants](/docs/platform-management/management-api/overview#getting-started) **Digital Trust Service** * Manage [Ecosystems](/docs/digital-trust-service) * Manage Ecosystem [participants](/docs/digital-trust-service/vical-guide#create-participants-and-add-issuer-certificates) * Manage Ecosystem [credential types](/docs/digital-trust-service/vical-guide#create-participants-and-add-issuer-certificates) * Manage Ecosystem [policies](/docs/digital-trust-service/vical-guide#manually-publish-a-vical) **Credential issuance** * [OID4VCI](/docs/issuance/oid4vci-overview): * Manage [Authentication providers](/docs/issuance/authorization-code/authentication-provider/overview) * Manage [Interaction hooks](/docs/issuance/authorization-code/interaction-hook/overview) * Manage [Claim sources](/docs/issuance/claims-source/overview) * Manage [Credential configurations](/docs/issuance/credential-configuration/overview) * Creating [Credential offers](/docs/issuance/credential-offer/overview) * [Direct issuance](/docs/issuance/direct-issuance-overview) of [CWT](/docs/issuance/cwt-direct-issuance) credentials\* * Managing [PDF](/docs/issuance/cwt-credential-templates/pdf-templates), [Apple](/docs/issuance/cwt-credential-templates/apple-templates) and [Google](/docs/issuance/cwt-credential-templates/google-templates) Digital Pass templates for CWT and Semantic CWT credentials\* **Credential management** * [Revoking](/docs/issuance/revocation/overview) CWT, Semantic CWT and mDoc credentials\* **Credential verification** * Verification of [mDocs](/docs/verification/remote-overview), [CWT](/docs/api-reference/platform/cwt-credentials-verification/verify-compact-credential) and [Semantic CWT](/docs/api-reference/platform/semantic-cwt-credentials-verification/verifyCompactSemantiCredential) credentials\* * Configure workflows for remote (online) verification of [mDocs](/docs/verification/remote-overview). * Manage [Reader certificates](/docs/verification/remote-web-verifiers/workflow#the-mattr-vii-tenant-returns-the-request-object) *** ### Issuer permissions [#issuer-permissions] The following list details the MATTR VII capabilities available to users and clients assigned with the *Issuer* role: **Platform management** * Manage [Custom domain](/docs/platform-management/custom-domain-overview) * Manage [DIDs](/docs/concepts/dids)\* * Manage [IACAs](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) * Manage [Webhooks](/docs/platform-management/webhooks-overview) **Digital Trust Service** * Retrieve Ecosystem [policies](/docs/digital-trust-service/vical-guide#manually-publish-a-vical)\* **Credential issuance** * [OID4VCI](/docs/issuance/oid4vci-overview): * Manage [Authentication providers](/docs/issuance/authorization-code/authentication-provider/overview) * Manage [Interaction hooks](/docs/issuance/authorization-code/interaction-hook/overview) * Manage [Claim sources](/docs/issuance/claims-source/overview) * Manage [Credential configurations](/docs/issuance/credential-configuration/overview) * Creating [Credential offers](/docs/issuance/credential-offer/overview) * [Direct issuance](/docs/issuance/direct-issuance-overview) of [CWT](/docs/issuance/cwt-direct-issuance) credentials\* * Managing [PDF](/docs/issuance/cwt-credential-templates/pdf-templates), [Apple](/docs/issuance/cwt-credential-templates/apple-templates) and [Google](/docs/issuance/cwt-credential-templates/google-templates) Digital Pass templates for CWT and Semantic CWT credentials\* **Credential management** * [Revoking](/docs/issuance/revocation/overview) CWT, Semantic CWT and mDoc credentials\* *** ### Managed Issuer permissions [#managed-issuer-permissions] The following list details the MATTR VII capabilities available to users and clients assigned with the *Managed Issuer* role: **Platform management** * Manage [Custom domain](/docs/platform-management/custom-domain-overview) * Manage [DIDs](/docs/concepts/dids)\* * Manage [Webhooks](/docs/platform-management/webhooks-overview) * Retrieve [IACAs](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) * Monitor [Analytics](/docs/platform-management/analytics/overview) information **Digital Trust Service** * Retrieve Ecosystem [policies](/docs/digital-trust-service/vical-guide#manually-publish-a-vical)\* **Credential issuance** * [OID4VCI](/docs/issuance/oid4vci-overview): * Manage [Authentication providers](/docs/issuance/authorization-code/authentication-provider/overview) * Manage [Interaction hooks](/docs/issuance/authorization-code/interaction-hook/overview) * Manage [Claim sources](/docs/issuance/claims-source/overview) * Creating [Credential offers](/docs/issuance/credential-offer/overview) * [Direct issuance](/docs/issuance/direct-issuance-overview) of [CWT](/docs/issuance/cwt-direct-issuance) credentials\* * Managing [PDF](/docs/issuance/cwt-credential-templates/pdf-templates), [Apple](/docs/issuance/cwt-credential-templates/apple-templates) and [Google](/docs/issuance/cwt-credential-templates/google-templates) Digital Pass templates for CWT and Semantic CWT credentials\* **Credential management** * [Revoking](/docs/issuance/revocation/overview) CWT, Semantic CWT and mDoc credentials\* *** ### Verifier permissions [#verifier-permissions] The following list details the MATTR VII capabilities available to users and clients assigned with the *Admin* role: **Platform management** * Manage [Custom domain](/docs/platform-management/custom-domain-overview) * Manage [DIDs](/docs/concepts/dids)\* * Manage [Messaging](/docs/api-reference/platform/messaging/signMessage)\* **Digital Trust Service** * Retrieve Ecosystem [policies](/docs/digital-trust-service/vical-guide#manually-publish-a-vical)\* **Credential verification** * Verification of [mDocs](/docs/verification/remote-overview), [CWT](/docs/api-reference/platform/cwt-credentials-verification/verify-compact-credential) and [Semantic CWT](/docs/api-reference/platform/semantic-cwt-credentials-verification/verifyCompactSemantiCredential) credentials\* * Configure workflows for remote (online) verification of [mDocs](/docs/verification/remote-overview). * Manage [Reader certificates](/docs/verification/remote-web-verifiers/workflow#the-mattr-vii-tenant-returns-the-request-object) *** ### DTS provider permissions [#dts-provider-permissions] The following list details the MATTR VII capabilities available to users and clients assigned with the *DTS provider* role: **Platform management** * Manage [Custom domain](/docs/platform-management/custom-domain-overview) * Manage [DIDs](/docs/concepts/dids)\* **Digital Trust Service** * Manage [Ecosystems](/docs/digital-trust-service) * Manage Ecosystem [participants](/docs/digital-trust-service/vical-guide#create-participants-and-add-issuer-certificates) * Manage Ecosystem [credential types](/docs/digital-trust-service/vical-guide#create-participants-and-add-issuer-certificates) * Manage Ecosystem [policies](/docs/digital-trust-service/vical-guide#manually-publish-a-vical) *** ### DTS consumer permissions [#dts-consumer-permissions] The following list details the MATTR VII capabilities available to users and clients assigned with the *DTS consumer* role: **Digital Trust Service** * Retrieve Ecosystem [policies](/docs/digital-trust-service/vical-guide#manually-publish-a-vical)\* *** ### Auditor permissions [#auditor-permissions] The following list details the MATTR VII capabilities available to users and clients assigned with the *Auditor* role: **Platform management** * Monitor [Analytics](/docs/platform-management/analytics/overview) information *** *\* Partial support or not available for users using [MATTR Portal](/docs/platform-management/portal); users or clients using [MATTR VII API](/docs/api-reference/) are not affected.* # Create a client for a tenant URL: /docs/platform-management/create-client A client represents an application or service that interacts with a tenant by making API requests. To enable this interaction, you need to create a client and assign it appropriate roles based on the capabilities it needs to access. This can be achieved either via the MATTR Portal or by using the [Management API](/docs/api-reference/management-api-reference-overview). 1. Open **Platform Management** in the left navigation panel and select **Tenant**.\ The tenant management screen appears. 2. Click **Create/Switch Tenant** at the top-right.\ The All Tenants window opens. 3. Click **Switch** next to the tenant created in the previous step. 4. Under **Platform Management**, select **Users, clients & roles**. 5. Go to the **Clients** tab and click **Create new**. 6. Enter a name to identify the client in the *Name* field (e.g. *Tutorial Issuer client*). 7. Select the required permissions in the *Tenant access* section. 8. Click **Create** to create the client 9. Make note of the displayed client credentials (`auth_url`, `tenant_url`, `client_id` and `client_secret`). The `client_secret` is only displayed immediately after the client is created. Once you navigate away from this screen, the client secret will be masked and cannot be retrieved again. Ensure you save it securely at this point. Make a request of the following structure to [create a new tenant client](/docs/api-reference/management/clients/createTenantClient): ```http title="Request" POST https://manage.au01.mattr.global/v1/tenants/{tenantId}/clients ``` * `tenantId` : Replace with the `id` identifying the tenant, obtained from the previous step's response. This creates the client in the context of a specific tenant. ```json title="Request Body" { "name": "Tutorial Issuer client", "roles": ["issuer"] } ``` * `name` : Name of the client associated with this tenant. * `roles` : An array of [roles](/docs/platform-management/access-control#role-permissions) assigned to this client based on the capabilities it needs to access. This example is assigning it the role of an [Issuer](/docs/platform-management/access-control#issuer-permissions). *Response* ```json title="Response body" { "clientId": "suC7I*******************************", "clientSecret": "Qn_43J****************************************************", "name": "Example client", "permissions": ["permission_1", "permission_2", "permission_3"], "roles": ["issuer"] } ``` * `clientId` : Client identifier for retrieving a tenant access token. * `clientSecret` : Client secret for retrieving a tenant access token. * `name` : As provided in the request. * `permissions` : An array of permissions assigned to the client based on the defined `roles`. * `roles` : As provided in the request. # API Reference URL: /docs/platform-management/custom-domain-api-reference ## Create a Custom domain [#create-a-custom-domain] ## Retrieve Custom domain [#retrieve-custom-domain] ## Update Custom domain [#update-custom-domain] ## Delete Custom domain [#delete-custom-domain] ## Verify Custom domain [#verify-custom-domain] # How to configure a Custom domain URL: /docs/platform-management/custom-domain-guide [Custom domains](/docs/platform-management/custom-domain-overview) represent your known and trusted brand, and can assist in instilling trust with your end-users when they interact with your MATTR VII tenant. ## Prerequisites [#prerequisites] * You must have an existing web domain. * You must have control over this web domain DNS records. * You must have a web service that runs on your web domain and can redirect or proxy requests. ## Overview [#overview] Configuring a [custom domain](/docs/platform-management/custom-domain-overview) for your MATTR VII tenant comprises the following steps: 1. [Create custom domain configuration](#create-custom-domain-configuration). 2. [Insert verification token into custom domain TXT record](#insert-verification-token-into-custom-domain-txt-record). 3. [Verify custom domain](#verify-custom-domain). 4. [Create redirects for required assets](#create-redirects-for-required-assets). 5. [Create a new `did:web` to identify the custom domain](#create-a-new-didweb-to-identify-the-custom-domain). ### Create a custom domain configuration [#create-a-custom-domain-configuration] This step can be performed either using either using the MATTR Portal or by making an API request. 1. Expand the **Platform Management** menu in the navigation panel on the left-hand side. 2. Select **Custom domain**. 3. Enter a meaningful name in the *Name* textbox (e.g. "My Custom Domain"). 4. Insert a suitable URL in the *Logo* URL textbox (e.g. `https://my-custom.site/logo.png`): * URL must be publicly available. * Must be a square image. * png and jpg files are supported. * Recommended size is 64x64 px. * Recommended maximum size is 15 KB. 5. Insert your full custom domain in the *Domain* textbox, leaving out the protocol (e.g. `my-custom.site`). 6. Select **Create**.\ Your Custom domain record is created and you must now verify it. 7. Copy the *DNS entry TXT record* value from the displayed record. You will need it in the next step. Make a request of the following structure to [configure your custom domain](/docs/platform-management/custom-domain-api-reference#create-a-custom-domain): ```http title="Request" POST /v1/config/domain ``` ```json title="Request body" { "name": "Custom Site", "logoUrl": "https://my-custom.site/logo-social.png", "domain": "my-custom.site" } ``` * `name` : Insert a name for the custom domain that will be displayed to digital wallet holders when they receive credential offers or verification requests from your MATTR VII tenant. * `logoUrl` : Insert a URL for a logo that will be displayed to digital wallet holders when they receive credential offers or verification requests from your MATTR VII tenant: * URL must be publicly available. * Must be a square image. * png and jpg files are supported. * Recommended size is 64x64 px. * Recommended maximum size is 15 KB. * `domain` : Insert the full custom domain, leaving out the protocol (e.g. https\://). *Response* ```json title="Response body" { "name": "Custom Site", "logoUrl": "https://my-custom.site/logo.png", "domain": "my-custom.site", "verificationToken": "a45ba33b-6c47-4349-7d49-a516c4d38406", // [!code focus] "isVerified": false // [!code focus] } ``` * `verificationToken` : This value must be added to your domain DNS entry TXT record in [step 2](#insert-verification-token-into-custom-domain-txt-record). * `isVerified` : This will indicate `false` until the domain has been verified as described in [step 3](#verify-custom-domain). MATTR VII will only use the custom domain after it has been verified and this field indicates `true`. Once the configuration is created, it is added to your tenant's `manifest.json` file. This is one of the files you will need to [create redirects](#create-redirects-for-required-assets) to. ### Insert verification token into custom domain TXT record [#insert-verification-token-into-custom-domain-txt-record] Insert the DNS entry TXT record (`verificationToken`) obtained in [step 1](##create-custom-domain-configuration) into a TXT record in your custom domain DNS entry. The exact steps to perform this will vary depending on your DNS service providers: * [Cloudflare](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/) * [GoDaddy](https://www.godaddy.com/en/help/add-a-txt-record-19232) * [AWS](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-creating.html) * [Google Cloud](https://cloud.google.com/dns/docs/records) * [Entrust](https://www.entrust.com/knowledgebase/ssl/how-to-add-txt-record-for-dns-verification-on-hover) * [Akamai Edge](https://learn.akamai.com/en-us/webhelp/edge-dns/edge-dns-user-guide/GUID-0033207B-AC09-44DF-8022-EC62ACAEB203.html) **Troubleshooting** If you can't set a TXT record for your custom domain because it already has a CNAME record, you can workaround this issue by performing the following: 1. Remove the CNAME record. 2. Add a TXT record with the correct verificationToken. 3. Verify your custom domain. 4. Remove the TXT record. 5. Return the CNAME record. ### Verify custom domain [#verify-custom-domain] Once your DNS provider has registered your TXT value and the change has been propagated, MATTR VII must reach out to the public DNS record and confirm the value matches the one set up on your tenant. This step can also be performed either using either using the MATTR Portal or by making an API request. 1. Expand the **Platform Management** menu in the navigation panel on the left-hand side. 2. Select **Custom domain**. 3. Select the **Verify** button in the Verification status panel.\ MATTR VII will attempt to verify your custom domain by checking the TXT record you created in [step 2](#insert-verification-token-into-custom-domain-txt-record). Upon successful verification, the displayed status should change from *Not verified* to *Verified*. Make a request of the following structure to verify your custom domain: ```http title="Request" POST /v1/config/domain/verify ``` *Response* * `204` : (with no body): Verification successful! Your custom domain is now active! * `400` : Something has gone wrong with the DNS setup. It could be that you need to wait longer to ensure your TXT record has propagated or repeat the process to verify domain ownership. * `404` : No custom domain configured on the tenant. Return to [step 1](#create-custom-domain-configuration) and configure a custom domain. ### Create redirects for required assets [#create-redirects-for-required-assets] For your custom domain to function properly, you will need to setup redirects to assets that are hosted on your MATTR VII tenant. Different redirect are required based on features used as part of your implementation: | Asset path | Description | Required | | :---------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------- | | `/manifest.json` | This file is required to provide digital wallet clients with information about your custom domain, such as its name and logo. | Always | | `/.well-known/did.json` | This is the `did:web` [document](/docs/concepts/dids#did-document) associated with your domain. It must be available for your `did:web` to be [resolvable](/docs/concepts/dids#resolving). | Only when issuing CWT credentials | | `/.well-known/did-configuration` | This file is used to establish trust in the [DID to domain linkage](/docs/concepts/dids#verification-relationships). This will ensure any digital wallet clients can resolve DIDs from your custom domain and verify the DID-to-domain linkage. | Only when issuing CWT credentials | | `/.well-known/openid-credential-issuer` | This file includes details required for interacting with your tenant as part of an [OID4VCI workflow](/docs/issuance/oid4vci-overview). | Only when issuing credentials using the OID4VCI workflow | | `/.well-known/oauth-authorization-server` | This file includes details required for interacting with your tenant as an OAuth authorization server as part of an [OID4VCI workflow](/docs/issuance/oid4vci-overview). | Only when issuing credentials using the OID4VCI workflow | | `/.well-known/oauth-client` | This file includes details required for interacting with your tenant as an OAuth client as part of an [mDocs online verification workflow](/docs/verification/remote-overview). | Only when verifying mDocs remotely | For example, if we were to set up `example.com` as a custom domain for the Learn MATTR VII tenant, we would need to create the following redirect for `/manifest.json` : | Origin | Redirect target | | :---------------------------------- | :-------------------------------------------------- | | `https://example.com/manifest.json` | `https://learn.vii.au01.mattr.global/manifest.json` | Once this redirect is setup, users attempting to interact with the custom domain will be served the asset from the Learn MATTR VII tenant. If any of the redirect paths are already used by your main website, you may need to create a separate subdomain for your MATTR VII custom domain. ### Create a new did:web to identify the custom domain (Optional) [#create-a-new-didweb-to-identify-the-custom-domain-optional] This step is only required if you are using your MATTR VII tenant to issue [CWT](/docs/concepts/cwt) credentials. When using your MATTR VII tenant to issue [CWT](/docs/concepts/cwt) credentials you must have a publicly available and resolvable [`did:web`](/docs/concepts/dids#didweb) that identifies this tenant as an issuer. When configuring a custom domain, you must create a new `did:web` that references the URL of your custom domain instead of your actual MATTR VII tenant. This means that when holders attempt to claim credentials issued by your MATTR VII tenant, they will see your custom domain as the issuer's identifier. [Create a did:web](/docs/api-reference/platform/dids/createDid) and use your custom domain as the `url` in the request body. MATTR VII will automatically host the generated DID document on your tenant, and make it available for any relying party attempting to [resolve](/docs/concepts/dids#resolving) this `did:web`. # Custom domain URL: /docs/platform-management/custom-domain-overview You can configure a custom domain for your MATTR VII tenant to represent your brand and instil trust with your end-users. For example, end-users might not feel comfortable claiming a credential issued from your MATTR VII tenant URL (e.g. `https://learn.vii.au01.mattr.global`), as they don’t know who or what MATTR is. Using your own domain might be a better approach. Custom domains don’t change how you interact with your tenant for administration functions and don’t prevent the existing tenant domain from being accessed. Since MATTR VII tenants are not exposed to end-users directly, the custom domains model we have implemented does not require full DNS mapping of a custom domain to a tenant (i.e. CNAME or A record). Instead, we allow setting a custom domain and then proving ownership using a TXT record on the DNS record. ## Requirements [#requirements] * Any MATTR VII tenant can only be linked to one custom domain. * You must be on a MATTR VII paid plan. * You must have an existing web domain. * You must have control over this web domain DNS records. * You must have a web service that runs on your web domain and can redirect or proxy requests. ## Process overview [#process-overview] Setting up a custom domain for your MATTR VII tenant comprises the following steps: 1. [Configure a custom domain](#configure-a-custom-domain). 2. [Verify domain ownership](#verify-domain-ownership). 3. [Verify your custom domain](#verify-your-custom-domain). 4. [Create required redirects](#create-required-redirects). ### Configure a custom domain [#configure-a-custom-domain] The first step is to make an API request to [create a custom domain configuration](/docs/platform-management/custom-domain-guide#create-custom-domain-configuration) on your MATTR VII tenant. This configuration defines the following: * Custom domain name (this is displayed to holders as they interact with your tenant). * Custom domain logo (this is displayed to holders as they interact with your tenant). * The domain the custom domain is hosted on. You may choose to create a separate subdomain from your main web presence to expose to end-users as part of managing their digital identities, or you may choose to run from your existing main website. The choice may come down to practical implementation details as you will need to setup [redirects](#create-required-redirects) to certain paths. If these paths are already or likely to be used by your main website, you may want to consider using them under a subdomain. Once the configuration is created, it is added to your tenant's `manifest.json` file. This is one of the files you will need to [create redirects](#create-required-redirects) to. ### Verify domain ownership [#verify-domain-ownership] After configuring your custom domain on MATTR VII, you must provide proof of ownership over the configured domain. To do this you must insert the verificationToken (obtained from the configuration response) into a TXT record in your custom domain DNS entry. ### Verify your custom domain [#verify-your-custom-domain] Once your DNS provider has registered your TXT value and the change has been propagated, MATTR VII must reach out to the public DNS record and confirm the value matches the one set up on your tenant. ### Create required redirects [#create-required-redirects] For your custom domain to function properly, you will need to setup redirects to assets that are hosted on your MATTR VII tenant. Different redirect are required based on features used as part of your implementation. Refer to the [Custom Domain guide](/docs/platform-management/custom-domain-guide#create-redirects-for-required-assets) for more information. # Environments and tenants URL: /docs/platform-management/environments-and-tenants ## Overview [#overview] MATTR VII is designed to support customers of all sizes with a flexible, secure, and scalable digital trust platform. At its core, MATTR VII enables multiple customers to operate independently and securely through a concept called **multi-tenancy**. Each customer operates in its own isolated space (a **tenant**), ensuring its data, credentials, and configuration remain private and protected. Depending on operational, compliance, and change management requirements, customers can choose between: * A **Standard Cloud deployment**, where infrastructure is securely and efficiently shared. * A **Dedicated Cloud deployment**, where an environment is provisioned exclusively for a single customer. This flexible model allows customers to balance cost efficiency, scalability, regulatory compliance, and operational control without compromising trust. ## What is a Tenant? [#what-is-a-tenant] MATTR VII implements a multi-tenant architecture, where a single logical platform instance serves multiple tenants. A tenant represents a distinct and isolated instance of MATTR VII within an environment. Each tenant maintains its own isolated: * **Data**: Credentials, presentations, and verification records * **Configuration**: Certificates, credential configurations, policies, and governance rules * **Access control**: OAuth clients, API keys, and permissions * **Operational state**: Activity logs, analytics, and audit trails Every Platform API request occurs within the context of a specific tenant. **Tenants are fully isolated**. No tenant can access another tenant's data, configuration, or credentials. A tenant in one environment cannot interact with or discover tenants in another environment. Isolation is enforced at both: * **Functional levels**: Ensuring strict data and access separation * **Non-functional levels**: Supporting request affinity and workload controls This ensures strong security boundaries while still enabling efficient shared infrastructure. ### Shared vs Isolated: What's the Difference? [#shared-vs-isolated-whats-the-difference] Understanding what is shared at the environment level versus what is isolated at the tenant level is key to understanding MATTR VII's architecture: **Shared at Environment Level** (applies to all tenants in the environment): * Platform version and software release * Core features and capabilities * Infrastructure and compute resources * API endpoints and service architecture * Platform-wide configuration settings (e.g. rate limits, logging levels) **Isolated at Tenant Level** (unique to each tenant): * Credentials, Certificates, and cryptographic keys * Issued credentials and presentation records * Credential configurations and templates * Governance policies and verification rules * OAuth clients and API access controls * User data and activity logs * Custom domains and branding This separation enables MATTR VII to deliver consistent platform functionality and efficient resource utilisation, while maintaining absolute data isolation and security between tenants. ## What is an Environment? [#what-is-an-environment] An environment is a deployment boundary for MATTR VII. An environment includes: * MATTR VII platform services * The MATTR Portal (one per environment) * Token endpoints and APIs * Supporting infrastructure * One or more tenants **Multiple** tenants can exist within a **single** environment. All tenants within an environment share: * **Platform version**: The same MATTR VII software release * **Feature availability**: The same core platform capabilities and features. Feature flags may exist on a tenant or environment level, but the underlying platform functionality is consistent across tenants in the same environment. * **Infrastructure**: Compute resources, network architecture, and platform services * **Release schedule**: Updates and patches are applied to the environment as a whole While tenants share certain environment-level characteristics, they remain fully isolated from each other in terms of data, credentials, and tenant-specific configurations. Environments enable customers to separate workloads, manage release promotion, and meet operational requirements. Environments can also be geographically separated to support regional, regulatory, or performance needs. ## Deployment Models [#deployment-models] MATTR VII supports two primary cloud deployment models: Standard Cloud and Dedicated Cloud. ### Standard Cloud [#standard-cloud] In the Standard Cloud model, customers receive a tenant in one of MATTR’s shared environments (deployed in a specific AWS region). Each environment hosts multiple tenants, with shared underlying infrastructure. Available regions currently include: * US West (Oregon) * Canada (Central) * Europe (Frankfurt) * Asia Pacific (Singapore) * Asia Pacific (Sydney) * Asia Pacific (New Zealand) What is **Shared**? * Underlying infrastructure * Compute resources * Platform services What is **Isolated**? * Tenant data * Credentials * Configuration * Access control policies * Governance rules All tenants receive the same core MATTR VII product functionality within the environment. Feature flags and configuration may influence what is enabled for a particular tenant. ### Dedicated Cloud [#dedicated-cloud] Dedicated Cloud is MATTR’s solution for customers who require a dedicated environment and/or customer-approved and coordinated change management. Dedicated Cloud includes: * One or more environments deployed in any AWS region * All customer data isolated within an individual AWS account that is not shared with other customers * Compute resources allocated to the Dedicated Cloud environment and shared only across that customer’s tenants Within each environment: * Multiple tenants can be created * Tenants remain fully isolated from one another * MATTR VII and MATTR Portal are deployed and released into that environment Dedicated Cloud includes enhanced operational controls such as: * Configurable platform event logging levels * Integration with customer SIEM solutions These capabilities provide improved visibility and alignment with enterprise security monitoring requirements. ## Isolating deployment stages [#isolating-deployment-stages] Most organisations need to separate their development, testing, and production workloads to reduce risk and manage changes safely. How you achieve this depends on your deployment model. ### Standard Cloud [#standard-cloud-1] Standard Cloud customers operate within a single shared environment and cannot provision separate environments. Instead, use **separate tenants** to represent each stage of your software development lifecycle. A common pattern is to use distinct tenants for: * Development * QA / testing * Staging * Production At a minimum, it is recommended to keep non-production and production workloads in separate tenants. Because each tenant is fully isolated — with its own credentials, configurations, access controls, and data — changes made in a development or staging tenant cannot affect your production tenant. Teams can maintain separate API client credentials and access controls per stage, and safely validate credential configurations and policies before promoting them. Note that all tenants within a Standard Cloud environment share the same platform version and release schedule. If your organisation requires independent release management or coordinated change control across stages, consider Dedicated Cloud. ### Dedicated Cloud [#dedicated-cloud-1] Dedicated Cloud supports provisioning **multiple environments** within a single customer account, enabling a traditional model of promoting changes progressively across distinct stages — for example, Development, QA, Staging, and Production. Each environment operates independently and can run at a different software version, giving teams full control over when platform updates are adopted at each stage. Changes validated in a lower environment can be promoted to the next, reducing risk before they reach production. ## Custom Domains [#custom-domains] Custom domains in MATTR VII allow organisations to present a trusted, branded experience to end-users, while maintaining secure and structured platform architecture. Custom domains can be applied at two levels: * Per Tenant (Custom Domain) * Per Environment (Environment Domain, Dedicated Cloud only) These serve different purposes and operate differently. ### Tenant-Level Custom Domains [#tenant-level-custom-domains] A tenant-level custom domain allows you to represent your brand when interacting with end-users. For example, an end-user may hesitate to claim a credential issued from a MATTR VII tenant URL such as `https://learn.vii.au01.mattr.global`, because the end-user may not recognise MATTR as the issuing authority. By configuring a custom domain (for example, `https://credentials.yourorganisation.com`), you can: * Reinforce brand recognition * Increase user confidence * Provide a seamless, trusted experience * Align credential flows with your organisation’s identity Refer to our [Custom Domain Overview](/docs/platform-management/custom-domain-overview) for more details on tenant-level custom domains. ### Environment-Level Domains [#environment-level-domains] Environment-level domains apply to the entire environment, not to an individual tenant. This configuration is typically used in dedicated cloud deployments. When applied at the environment level, a domain can be configured for: * The MATTR VII base API endpoint * The OAuth token endpoint * MATTR Portal * DTS (Digital Trust Service) website Unlike tenant-level custom domains, environment-level domains are part of the infrastructure and deployment configuration, rather than a branding mechanism for a specific tenant. # Platform Management URL: /docs/platform-management MATTR platforms are designed to empower customers with the tools and flexibility to self-onboard, configure, and integrate in the way that best suits their needs. Whether you’re building programmatically with APIs or working through an intuitive web interface, you can manage your tenant securely and confidently. At the heart of Platform Management are features that simplify administration, streamline integrations, and enable visibility and control: * [Management APIs](/docs/platform-management/management-api/overview): A comprehensive set of management tools that allow you to create and manage tenants, control access, and define role-based permissions. These APIs provide a foundation for automation and seamless integration into your existing systems. * [MATTR Portal](/docs/platform-management/portal): A graphical user interface built on the MATTR VII platform and Management APIs. The Portal provides a secure, role-based environment where administrators can easily configure integrations, manage tenant settings, and oversee platform operations—all without needing to write code. * [Access Control](/docs/platform-management/access-control): Fine-grained controls to define who can access tenants and what actions they are permitted to perform. Role-based access ensures your teams have the right permissions while maintaining strong governance and security. * [Analytics Tools](/docs/platform-management/analytics/overview): Built-in monitoring capabilities to track tenant activity, giving you insight into usage patterns and system behaviour. Analytics help you make informed decisions and maintain confidence in your platform operations. * [Webhooks](/docs/platform-management/webhooks-overview): Event-driven integration that connects MATTR platforms to your wider business ecosystem. Configure Webhooks to respond to platform events in real-time, triggering downstream workflows and keeping systems in sync. * [Custom Domain](/docs/platform-management/custom-domain-overview): Configure your tenant to operate under a domain that represents your brand. A custom domain not only instils trust with your end-users but also ensures a seamless and branded experience. ## Underlying platforms [#underlying-platforms] MATTR Platform management capabilities All platform management capabilities are provided through MATTR VII APIs, accessible via the MATTR Portal or direct Management API requests. # MATTR Portal URL: /docs/platform-management/portal ## Overview [#overview] The MATTR Portal is built on top of MATTR VII Platform and Management APIs and provides an easy-to-use interface to effectively manage and oversee tenant management and configuration. Not all features available via the MATTR VII Platform and Management APIs are currently supported in the Portal. Refer to the [Capabilities](#capabilities) section below for a list of supported functionalities. ## Getting Started [#getting-started] ### Creating a tenant [#creating-a-tenant] Every action in the Portal happens within the context of a specific tenant. Tenants represent distinct instances of MATTR VII, each with its own configuration, credentials, and governance. When you sign in to the Portal, you can easily switch between all the tenants you have access to, enabling streamlined oversight without compromising clarity or security. 1. Log into the [MATTR Portal](https://portal.mattr.global). 2. Select the **Create/switch tenant** button on the top-right side of the screen.\ The *All tenants* panel is displayed, listing any existing tenants. 3. Select the **Create new** button.\ The *New tenant* form is displayed. 4. Use the *Region* dropdown list to select the region your tenant will be hosted in. 5. Use the *Tenant subdomain* text box to insert a subdomain for your tenant (e.g. `my-first-tenant`). 6. Use the *Tenant name* text box to insert a meaningful and friendly name for your tenant (e.g. *My first tenant*). 7. Select the **Create** button to create the new tenant. 8. Copy the displayed tenant information (`audience`, `auth_url`, `tenant_url`, `client_id` and `client_secret`) which is required for the next step. The `client_secret` is only displayed immediately after the client is created. Once you navigate away from this screen, the client secret will be masked and cannot be retrieved again. Ensure you save it securely at this point. ### Interacting with the tenant [#interacting-with-the-tenant] You can interact with your tenant and use MATTR VII capabilities either using the [MATTR Portal](/docs/platform-management/portal) or via direct API calls. The Portal provides a user-friendly interface for managing your tenant, while the APIs allow for programmatic access to MATTR VII capabilities. You can use the Portal's various [features and functionalities](/docs/platform-management/portal) to interact with your tenant and use MATTR VII capabilities: * Every action in the portal is within the context of a specific tenant. This means that all configurations, settings and data you manage are associated with the selected tenant. * Use the drop-down list in the top-left corner to switch between tenants. * Use the navigation panel on the left-hand side to access different functionalities. **Capabilities** The Portal currently supports the following functionalities: * **Platform management**: Manage your MATTR VII tenants: * Tenant management: Create, view and delete tenants. This capability is based on the Management APIs. * Users, clients & roles: Manage users and clients for your tenants. These capabilities are based on the [Clients](/docs/api-reference/management/clients/createTenantClient) and [Members](/docs/api-reference/management/members/inviteTenantMember) endpoints in the Management API. * [Custom domain](/docs/platform-management/custom-domain-guide): Configure a [Custom domain](/docs/platform-management/custom-domain-overview) for the selected tenant. This capability is based on [configuring a Custom domain](/docs/platform-management/custom-domain-guide#create-custom-domain-configuration) using an [API request](/docs/platform-management/custom-domain-api-reference#create-a-custom-domain) and [verifying the Custom domain](/docs/platform-management/custom-domain-guide#verify-custom-domain) using an [API request](/docs/platform-management/custom-domain-api-reference#verify-custom-domain). * Monitoring: Query and inspect analytic events in your environment. This capability is based on the [Analytic APIs](/docs/platform-management/analytics/overview). * Webhooks: Create a [Webhook](/docs/platform-management/webhooks-overview) to subscribe to events. Available options are similar to those described for [creating a Webhook](/docs/platform-management/webhooks-guide) using an [API request](/docs/platform-management/webhooks-api-reference#create-a-webhook). * DIDs: View Decentralized Identifiers (DIDs) available on your tenant. This includes any [did:key](/docs/concepts/dids#didkey) and/or [did:web](/docs/concepts/dids#didweb) available on your tenant. Note that you cannot use the SSP to create DIDs. * Certificates: Manage [IACAs](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca), [DTS CA](/docs/digital-trust-service/certificates-overview) and [Verifier root CA](/docs/verification/certificates/overview) certificates on your tenant. * **Digital Trust Service**: Manage your DTS: * Create and manage [participants](/docs/digital-trust-service/vical-guide#create-participants-and-add-issuer-certificates). * Create and manage [credential types](/docs/digital-trust-service/vical-guide#create-participants-and-add-issuer-certificates). * Publish the DTS' [policy](/docs/digital-trust-service/vical-guide#manually-publish-a-vical) and control auto-publishing capabilities. * **Credential issuance**: Manage [OID4VCI](/docs/issuance/oid4vci-overview) workflow components and configuration: * Authentication provider: Configure and edit an authentication provider to be used during credential issuance flows. Available options are similar to those described for [configuring an Authentication provider](/docs/issuance/authorization-code/authentication-provider/guide) using an [API request](/docs/issuance/authorization-code/authentication-provider/api-reference#configure-an-authentication-provider). * Interaction hook: Configure an interaction hook to redirect a user to a custom component during the credential issuance journey. Available options are similar to those described for [configuring an Interaction hook](/docs/issuance/authorization-code/interaction-hook/tutorial) using an [API request](/docs/issuance/authorization-code/interaction-hook/api-reference#create-an-interaction-hook). * Claims sources: Configure and edit claims sources to fetch claims from an external endpoint and use them when issuing credentials. Available options are similar to those described for [configuring a Claims source](/docs/issuance/claims-source/overview) using an [API request](/docs/issuance/claims-source/api-reference#configure-a-claims-source). * Credential configurations: Create mDocs, CWT and Semantic CWT credential configurations. Available options are similar to those described for [creating a Credential configuration](/docs/issuance/credential-configuration/overview) using an API request. * Credential offer: Create a credential offer by specifying credential configurations and request parameters. This capability is based on [creating a Credential offer](/docs/issuance/credential-offer/guide) using an [API request](/docs/issuance/authorization-code/api-reference#create-credential-offer), with some additional capabilities to share the offer with the intended holder. * **Credential verification**: Configure [mDocs online verification workflows](/docs/verification/remote-web-verifiers/workflow): * Trusted issuers: Configure and manage mDocs issuers that can be trusted when verifying mDocs presented online. Available options are similar to those described for [creating a trusted issuer](/docs/verification/remote-verification-api-reference/trusted-issuers#create-a-trusted-issuer) using an API request. * Supported wallets: Configure and manage digital wallet applications that can present mDocs online for verification, and how to interact with these wallets. Available options are similar to those described for [creating a wallet provider](/docs/verification/remote-verification-api-reference/wallet-providers#create-a-wallet-provider) using an API request. * Applications: Configure and manage applications that can create mDocs online verification sessions, and how to interact with these applications. Available options are similar to those described for [creating a verifier application](/docs/verification/remote-verification-api-reference/verifier-applications#create-a-verifier-application) using an API request. **Roles and permissions** The Portal is designed to be used by different roles within an organization. The MATTR Portal UI is aligned with the user's role and the permissions assigned to them. This means that users will only see the features and functionalities that are relevant to their role. * When you create a tenant, you are automatically assigned an `admin` role for that tenant. This role grants you full access to manage all aspects of the tenant. * If you are invited to manage a tenant created by someone else, your assigned role may differ. This means you might have access to a limited set of features based on your permissions. For more details on on available roles and associated permissions, refer to the [Access Control documentation](/docs/platform-management/access-control). Perform the following steps to interact with your MATTR VII tenant via APIs: **Choose an endpoint** Select a MATTR VII endpoint to make a request to. The following resources might be helpful: * The [API Reference](/docs/api-reference) offers an exhaustive list of all available endpoints and their request structures in different languages. * Different tutorials and guides can be used to learn what endpoints are required for specific capabilities and workflows. * Refer to the [Access control](/docs/platform-management/access-control) section to learn more about what endpoints your client will be able to access. We recommend using the MATTR VII [Postman collection](/docs/api-reference#postman-collection) to make requests to your MATTR VII tenant. While this isn't an explicit prerequisite it can really speed things up. **Obtain an access token** Most of the MATTR VII endpoints are protected and require providing a bearer access token when making a request. If you are making a request to an unprotected endpoint (as detailed in the [API Reference](/docs/api-reference)), you do not need to obtain an access token and can continue to the next step. Use your [access credentials](/docs/platform-management/portal#getting-started) and make a request of the following structure to obtain an access token: ```http title="Request" POST https://{auth_server}/oauth/token ``` * `auth_server` : Replace with the `auth_url` value obtained when you created your tenant. ```json title="Request Body" { "client_id": "F5qae****************************", "client_secret": "Wzc8J**********************************************************", "audience": "learn.vii.au01.mattr.global", "grant_type": "client_credentials" } ``` * `client_id` : Replace with the `client_id` value obtained when you created your tenant. * `client_secret` : Replace with the `client_secret` value obtained when you created your tenant. * `audience` : Replace with the `audience` value obtained when you created your tenant. * `grant_type` : Always use `client_credentials` as a static value, regardless of your specific [login credentials](/docs/platform-management/portal#getting-started). *Response* ```json title="Response body" { "access_token": "eyJhb********************************************************************", // [!code focus] "expires_in": 14400, "token_type": "Bearer" } ``` * The returned `access_token` will enable access to endpoints as per the role assigned to the client. Refer to [Access control](/docs/platform-management/access-control) for more Information. * You will need to obtain a new access token whenever it expires. Our [Postman collection](/docs/api-reference#postman-collection) includes a pre-request script that obtains an access token when it is missing or has expired. **Construct the request** Construct an API request using the selected endpoint path and the `tenant_url` value obtained when you created your tenant: ```http title="Request template" {method} https://{tenant_url}/{path} ``` For example, a request to [retrieve all IACAs](/docs/issuance/certificates/api-reference/iaca#retrieve-all-iacas) from a tenant whose `tenant_url` is `learn.vii.au01.mattr.global` should be constructed as follows: ```http title="Request example" GET https://learn.vii.au01.mattr.global/v2/credentials/mobile/iacas ``` If the operation has a request body you should structure it too, based on the details provided in the [API Reference](/docs/api-reference) or relevant tutorial. Whatever tool or language your are using to make the request, make sure you include the `access_token` in the request header when making requests to protected endpoints. Refer to the [API Reference](/docs/api-reference) for request samples. **Handle the response** The endpoint would respond with a standard [HTTP status code](https://www.rfc-editor.org/rfc/rfc9110.html#name-status-codes) and a response body. These differ between endpoints and are detailed in the [API Reference](/docs/api-reference). You can now adjust your implementation to handle these responses to achieve the desired outcome. ### Inviting users [#inviting-users] To support collaboration, you can invite other users to access the MATTR Portal and manage tenants that you administer. When you invite a user to manage a tenant, you select the role they will hold within that tenant. This role defines what they can view, modify, or manage—ensuring fine-grained access control that aligns with your trust and governance requirements. Perform the following steps to invite a user to interact with a tenant you administer in the MATTR Portal: 1. Open **Platform Management** in the left navigation panel and select **Tenant**.\ The tenant management screen appears. 2. Click **Create/Switch tenant** at the top-right.\ The All Tenants window opens. 3. Click **Switch** next to the desired tenant. 4. Under **Platform Management**, select **Users, clients & roles**. 5. Go to the **Users** tab and click **Invite**. 6. Enter the user's email in the *Email* field. 7. Select roles for the user using the checkboxes in the *Tenant access* section. 8. Click **Invite** to send the invitation. * If the invited user already has access to the Portal, they will immediately see the new tenant in their list of accessible tenants. When they select this tenant, their Portal UI would be updated to reflect the permissions associated with their role in that tenant. * If the invited user does not have access to the Portal yet, they’ll receive an email to accept the invite and log into the Portal. Invites expire after 5 days. By default, invited users are assigned to the **Contributor** plan. As Contributors, they can perform all actions permitted by their assigned role within the specific tenant they were invited to. However, they cannot create new tenants or switch to other tenants unless they are explicitly invited to those tenants and/or are given the necessary permissions. Inviting users is only supported for Portal users that are members of the tenant. Machine-to-machine (M2M) clients cannot invite users. Calling the [invite a tenant member](/docs/api-reference/management/members/inviteTenantMember) endpoint with an M2M client token returns a `404 Resource Not Found` response, even when the client holds an `admin` role. If you provision a tenant programmatically using an M2M client, that client is not a Portal member of the tenant and therefore cannot invite users. To manage tenant membership, create the tenant from the Portal as the steps above describe, so the creating Portal user becomes a member automatically. You can then invite additional users from the Portal UI or by calling the invitation endpoint with that Portal user's access token. ### Being invited to a tenant [#being-invited-to-a-tenant] Another user with sufficient privileges can invite you to manage a tenant they control. In this case: * You are assigned a specific role as part of the invitation. * Your permissions in that tenant are determined by the role you’re given—limiting or enabling specific actions according to that scope. * You can work across multiple tenants, each with different roles depending on how you've been invited. ## Single Sign-On (SSO) [#single-sign-on-sso] The MATTR Portal supports Single Sign-On (SSO) as a paid add-on, allowing your team to authenticate using your organization's existing identity provider (IdP). [Contact us](mailto:dev-support@mattr.global) to learn more and enable SSO for your organization. ### Supported identity providers [#supported-identity-providers] MATTR supports any identity provider that integrates with Auth0 as an enterprise connection. This includes providers such as Microsoft Entra ID, Google Workspace, Okta, ADFS, and SAML-based IdPs. Refer to the [Auth0 Enterprise Identity Providers](https://auth0.com/docs/authenticate/identity-providers/enterprise-identity-providers/enable-enterprise-connections) documentation for a full list of supported providers. ### Enabling SSO [#enabling-sso] To enable SSO for your organization, provide the following to the MATTR team: * **Email domain**: The email domain used by your organization (e.g. `your-company.com`). * **Logo**: A logo to display on the Portal login page for your organization. Once received, the MATTR team will configure SSO for your domain and onboard your first user. ### Managing SSO users [#managing-sso-users] After SSO is enabled, your first user is onboarded by the MATTR team. From there, you can [invite additional users](#inviting-users) to specific tenants, and they will be able to log into the Portal via SSO. Enabling SSO does not automatically grant all users in your organization access to the Portal. Each user must be explicitly [invited to a tenant](#inviting-users) before they can log in. This ensures that access to tenants and their associated data remains tightly controlled. ## What's next? [#whats-next] We recommend starting with one of the following resources based on your needs: * For issuers: * [OID4VCI Authorization Code tutorial](/docs/issuance/authorization-code/tutorial): Learn how to configure an OID4VCI Authorization Code flow. * [OID4VCI Pre-authorized Code tutorial](/docs/issuance/pre-authorized-code/tutorial): Learn how to configure an OID4VCI Pre-authorized Code flow. * [Revocation tutorial](/docs/issuance/revocation/tutorial): Learn how to issue revocable credentials and manage their revocation status. * For verifiers: * [Remote web verification tutorial](/docs/verification/remote-web-verifiers/tutorial): Learn how to build a web application that can verify mDocs remotely. * [Remote mobile verification tutorial](/docs/verification/remote-mobile-verifiers/tutorial): Learn how to build a mobile application that can verify mDocs remotely. * General: * [API reference](/docs/api-reference): Explore the API reference documentation to understand the available endpoints, request/response formats, and authentication methods. * [Postman collection](/docs/api-reference#postman-collection): Download the Postman collection to quickly test and interact with the APIs. The collection includes pre-configured requests for common operations, making it easier to get started with API development. # Roles and Permissions URL: /docs/platform-management/roles-and-permissions This page lists the MATTR VII platform [roles](/docs/platform-management/access-control) and the full set of endpoints each role is authorized to access. # API Reference URL: /docs/platform-management/webhooks-api-reference ## Create a Webhook [#create-a-webhook] ## Retrieve all Webhooks [#retrieve-all-webhooks] ## Retrieve a Webhook [#retrieve-a-webhook] ## Update a Webhook [#update-a-webhook] ## Delete a Webhook [#delete-a-webhook] ## Retrieve Webhook JWKs [#retrieve-webhook-jwks] # How to create and use a Webhook URL: /docs/platform-management/webhooks-guide MATTR VII [Webhooks](/docs/platform-management/webhooks-overview) enable retrieving information that is generated during an API interaction but is not included in the request or response payloads. ## Prerequisites [#prerequisites] * A configured MATTR VII workflow that utilizes a supported Webhook event type: * [OID4VCI workflow](/docs/issuance/oid4vci-overview). ## Overview [#overview] Creating and using a Webhook hook comprises the following steps: 1. [Create a MATTR VII Webhook](#create-a-mattr-vii-webhook). 2. [Verify the Webhook request](#verify-webhook-requests). 3. [Use the Webhook payload](#use-webhook-events-payload). ### Create a MATTR VII Webhook [#create-a-mattr-vii-webhook] This step can be performed either via the MATTR Portal or via the MATTR VII APIs. 1. Log in to the [MATTR Portal](/docs/platform-management/portal) and navigate to the **Webhooks** section under **Platform Management**. 2. Click on **Create new**. 3. Use the *Event* type checkbox to select **Open ID credential issued**. 4. In the *URL* field, enter the HTTPS endpoint where you want to receive the Webhook events. 5. Expand the *Verify request* panel and copy the *Webhook ID* value. You will need it to verify the Webhook requests in the next step. Make a request of the following structure to [create a Webhook](/docs/platform-management/webhooks-api-reference#create-a-webhook): ```http title="Request" POST /v1/webhooks ``` ```json title="Request body" { "events": ["OpenIdCredentialIssued"], "url": "https://example.com" } ``` * `events` : This array includes the event types that will trigger this Webhook. Currently allowed types: * `OpenIdCredentialIssued` : Triggered upon completion of an [OID4VCI workflow](/docs/issuance/oid4vci-overview). * `OpenIdCredentialIssuedSummary` : Triggered upon completion of an [OID4VCI workflow](/docs/issuance/oid4vci-overview) but only provides a summary of the issuance event, leaving out the credential object. * `url` : This is the URL that will receive the Webhook events data payload when they are triggered by MATTR VII for the specified events: * Must be a valid URL. * Must use the HTTPS protocol. * Must not be an IP address. * Must not include query parameters or fragments. * Non-ASCII characters are normalized. * Must return a 2xx response, otherwise it will go through a retry cycle and eventually fail. *Response* ```json title="Response body" { "id": "0c099611-19c4-4f29-8724-6b9e5ba1ef7c", // [!code focus] "events": ["OpenIdCredentialIssued"], "url": "https://example.com", "disabled": false // [!code focus] } ``` * `id` : Unique identifier for the created Webhook. You will need it to [verify Webhook events](#verify-webhook-requests). * `disabled` : Set to `false` by default, indicating the Webhook is active. When set to `true` the Webhook is disabled and no events are triggered and sent to the defined `url`. You can disable an active Webhook by [making an update request](/docs/platform-management/webhooks-api-reference#update-a-webhook). ### Verify Webhook requests [#verify-webhook-requests] Once the Webhook is configured, MATTR VII will make a POST request to the specified `url` each time an event is triggered. The request will include a payload with the event data. To ensure the integrity and authenticity of the request, it is important to verify that the request is indeed from MATTR VII and not from a malicious actor. To do this, you can use the following methods: 1. Compare the Webhook ID (`id`) that is obtained when [creating a MATTR VII Webhook](#create-a-mattr-vii-webhook) with the `webhookId` specified in the [event payload](#use-webhook-events-payload). 2. Verify the request HTTP signature. **Verifying HTTP signatures** All MATTR VII Webhook requests are signed using [HTTP Message Signatures](https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/) (an IETF draft standard). You can retrieve the public keys MATTR VII uses to sign the HTTP and use them to verify the HTTP signature. Make the following request to [retrieve the public keys](/docs/platform-management/webhooks-api-reference#retrieve-webhook-jwks): ```http title="Request" GET /v1/webhooks/jwks ``` This endpoint is publicly available by design and does not require authentication. *Response* ```json title="Response body" { "keys": [ { "kty": "OKP", "crv": "Ed25519", "kid": "1608085995", "x": "1NYsB58B9bNmReXqyQR8R_DeJtoLHSW-JsyZVmV2EWQ" } ] } ``` * `kid` : Use this key to identify which key a particular HTTP request is signed with. You can cache the response as these public keys are not expected to change often. **Verification implementations** * To facilitate verification of MATTR VII Webhook request, we provide a [typescript based library](https://www.npmjs.com/package/@mattrglobal/http-signatures) that can be used for verification or serve as a reference implementation to develop a verification SDK in another programming language. * To learn more about verifying our Webhook requests, have a look at the [Open Source MATTR Http-Signatures library](https://github.com/mattrglobal/http-signatures). ### Use Webhook events payload [#use-webhook-events-payload] The event payload has a common structure in JSON format: ```json title="Event payload structure" { "deliveryId": "9a8b0b72-5c4f-4925-b101-528edbb5d75d", "deliveryTimestamp": "2022-08-30T01:26:38.325Z", "webhookId": "5be4f663-af93-41d8-8542-f5c9a2a2d588", "event": { "id": "de347800-d56f-4262-9f19-52b34856a933", "type": "OpenIdCredentialIssued", "payload": "{event payload}", "timestamp": "2022-08-30T01:26:38.308Z" } } ``` * `deliveryId` : The unique identifier assigned each time the event is delivered to a Webhook endpoint. * `deliveryTimestamp` : The time when the event was delivered. * `webhookId` : The unique Webhook identifier returned in the [response](#response) during its creation. * `event` : Includes the following event details: * `id` : Unique event identifier. * `type` : Either `OpenIdCredentialIssued` or `OpenIdCredentialIssuedSummary`. * `payload` : Contains the specific event data. Refer to [Event payloads](#event-payload) below for examples. * `timestamp` : Specifies the date/time when the event was created. When a Webhook event is sent from MATTR VII, `x-mattr-webhook-id` and `x-mattr-event-type` are added as request headers. MATTR VII does not guarantee the delivery of events in the exact order that they are generated or that no duplicate events will be received by the Webhook endpoint. You can safeguard against duplicates by checking the `event.id` that is provided in the event payload, this is a unique identifier for each event generated by MATTR VII. Alternatively, make your event processing idempotent. #### Timeout and retry [#timeout-and-retry] When a Webhook fails to send data to the configured `url`, or when a session timeout occurs, a maximum of three retry attempts are made at set intervals. The interval time increases exponentially according to the number of retries that have been attempted. If the Webhook fails to send following the retry attempts, it will be marked as failed. By default, the Webhook has a response timeout of three seconds. If the server receiving the Webhook event does not respond within this period it is considered a failure. #### Event payload [#event-payload] Below are examples of event payloads: *mDocs* ```json title="mDocs event payload example" { "format": "mso_mdoc", "userId": "7382276d-ef75-4d17-8fb0-1d3aec4647ab", "credentialProfile": "mobile", "credentialConfigurationId": "1d8c7ad7-84ce-4519-8365-7af986e4ee0e", "credentialOfferId": "3b4f5cf6-4069-4c51-bbed-8165b4f9a889", "credentialId": "9613ac5e-a0ba-4512-ba0b-90e91b2744bc", "userClaims": { "Name": "John" }, "credential": "{base64url_encoded_credential}" } ``` * `format` : Issued credential format (For mDocs this would always be `mso_mdoc`). * `userId` Unique identifier of the user the credential was issued to on the MATTR VII tenant. * `credentialProfile` : Issued credential profile (For mDocs this would always be `mobile`). * `credentialOfferId` : Unique identifier of the [Credential Offer](/docs/issuance/credential-offer/overview) that was used to initiate the issuance flow. This optional field is only included for credentials issued via the [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview). * `credentialConfigurationId` : Unique identifier of the [Credential configuration](/docs/issuance/credential-configuration/overview) that was used to issue the credential. * `credentialId` : Unique identifier of the issued credential on the MATTR VII tenant. * `userClaims` : Claims that were collected for this user as part of the issuance workflow and persisted on the MATTR VII tenant. * `credential` : Base64 encoded byte\[] of the issued mDoc. *CWT credentials* ```json title="CWT credentials event payload example" { "format": "cwt", "userId": "7382276d-ef75-4d17-8fb0-1d3aec4647ab", "credentialProfile": "compact", "credentialConfigurationId": "1d8c7ad7-84ce-4519-8365-7af986e4ee0e", "credentialId": "9613ac5e-a0ba-4512-ba0b-90e91b2744bc", "userClaims": { "Name": "John" }, "credential": "{base64url_encoded_credential}" } ``` * `format` : Issued credential format (For CWT Credentials this would always be `cwt`). * `userId` Unique identifier of the user the credential was issued to on the MATTR VII tenant. * `credentialProfile` : Issued credential profile (For CWT credentials this would always be `compact`). * `credentialConfigurationId` : Unique identifier of the [Credential configuration](/docs/issuance/credential-configuration/overview) that was used to issue the credential. * `credentialId` : Unique identifier of the issued credential on the MATTR VII tenant. * `userClaims` : Claims that were collected for this user as part of the issuance workflow and persisted on the MATTR VII tenant. * `credential` : Base64 encoded byte\[] of the issued CWT credential. ```json title="mDocs event payload example" { "format": "mso_mdoc", "userId": "7382276d-ef75-4d17-8fb0-1d3aec4647ab", "credentialProfile": "mobile", "credentialConfigurationId": "1d8c7ad7-84ce-4519-8365-7af986e4ee0e", "credentialOfferId": "3b4f5cf6-4069-4c51-bbed-8165b4f9a889", "credentialId": "9613ac5e-a0ba-4512-ba0b-90e91b2744bc", "userClaims": { "Name": "John" } } ``` * `format` : Issued credential format: * `cwt` : CWT credentials. * `mso_mdoc` : mDocs. * `userId` Unique identifier of the user the credential was issued to on the MATTR VII tenant. * `credentialProfile` : Issued credential profile: * `compact` : CWT credentials. * `mobile` : mDocs. * `credentialConfigurationId` : Unique identifier of the [Credential configuration](/docs/issuance/credential-configuration/overview) that was used to issue the credential. * `credentialOfferId` : Unique identifier of the [Credential Offer](/docs/issuance/credential-offer/overview) that was used to initiate the issuance flow. This optional field is only included for credentials issued via the [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview). * `credentialId` : Unique identifier of the issued credential on the MATTR VII tenant. * `userClaims` : Claims that were collected for this user as part of the issuance workflow and persisted on the MATTR VII tenant. MATTR VII does not guarantee the delivery of events in the exact order that they are generated or that no duplicate events will be received by the Webhook endpoint. You can safeguard against duplicates by checking the `deliveryId`. Alternatively, make your event processing idempotent. # Webhooks URL: /docs/platform-management/webhooks-overview ## Overview [#overview] MATTR VII Webhooks enable retrieving information that is generated during an API interaction but is not included in the request or response payloads. You can subscribe to specific events that are triggered on set MATTR VII operations to retrieve the required information whenever it is generated. When the subscribed event is triggered, the information relating to that event is published via the Webhook through to the URL(s) configured on the subscription. The following MATTR VII event types can be subscribed to: * `OpenIdCredentialIssued` : Triggered upon completion of an [OID4VCI workflow](/docs/issuance/oid4vci-overview). * `OpenIdCredentialIssuedSummary` : Triggered upon completion of an [OID4VCI workflow](/docs/issuance/oid4vci-overview) but only provides a summary of the issuance event, leaving out the `credential` object. One possible use-case for these event payloads is to provide the DID that was used as the `credentialSubject.id` when binding and issuing the credential to the subject’s wallet. MATTR VII does not guarantee the delivery of events in the exact order that they are generated or that no duplicate events will be received by the Webhook endpoint. You can safeguard against duplicates by checking the event.id that is provided in the event payload, this is a unique identifier for each event generated by MATTR VII. Alternatively, make your event processing idempotent. ## Webhook validation [#webhook-validation] To validate the integrity and authorship of Webhooks generated by the MATTR VII platform, all Webhook events are signed using [HTTP Message Signatures](https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/) (an IETF draft standard). MATTR strongly encourages verifying each Webhook event to provide a suitable level of protection to integrations consuming the event information: * Verify the HTTP signatures. * Compare the `webhookId` identifier that is generated when the Webhook is created to the `webhookId` specified in the request. ### Verifying HTTP signatures [#verifying-http-signatures] You can [obtain](/docs/platform-management/webhooks-api-reference#retrieve-webhook-jwks) the public keys MATTR VII uses to sign the HTTP and use them to verify the HTTP signature. The response key set is relatively static and only expected to change on rare occasions, such as when performing key rotation. To facilitate verification of MATTR VII Webhook requests, we provide a [typescript-based library](https://www.npmjs.com/package/@mattrglobal/http-signatures) that can be used for verification or serve as a reference implementation to develop a verification SDK in another programming language. To learn more about verifying our Webhook requests, take a look at the [Open Source MATTR Http-Signatures library](https://github.com/mattrglobal/http-signatures). ## Timeouts and retry [#timeouts-and-retry] When a Webhook fails to send data to the configured URL, or when a session timeout occurs, a maximum of three retry attempts are made at set intervals. The interval time increases exponentially according to the number of retries that have been attempted. If the webhook fails to send following the retry attempts, it will be marked as failed. By default, the Webhook has a response timeout of three seconds. If the server receiving the Webhook event does not respond within this period it is considered a failure. # Learn how to consume MATTR VII Webhooks URL: /docs/platform-management/webhooks-tutorial ## Overview [#overview] In an [OID4VCI issuance workflow](/docs/issuance/oid4vci-overview), you may want to trigger additional actions once a credential has been successfully issued. For example, sending a notification, updating a database, or invoking a downstream system. [MATTR VII Webhooks](/docs/platform-management/webhooks-overview) provide a simple way to achieve this by notifying consuming applications when specific events occur. This tutorial will guide you through creating a webhook in your MATTR VII tenant and using a sample application to consume the webhook, verify the request, and display the event payload when a credential is issued. You can then adapt principles demonstrated in this tutorial to suit your specific use cases. ## Tutorial overview [#tutorial-overview] The current tutorial builds upon the [Authorization Code](/docs/issuance/authorization-code/tutorial) or [Pre-authorized Code](/docs/issuance/pre-authorized-code/tutorial) tutorials by adding the following steps: 1. **Create a MATTR VII Webhook:** Configure MATTR VII to send a Webhook event when a credential is issued. 2. **Set up a webhook receiver sample app:** Use a sample Node.js application that achieves the following: * Exposes a public endpoint to receive Webhook events. * Verifies the authenticity of incoming requests using MATTR VII's public keys. * Logs the received event payload to the console. ## Prerequisites [#prerequisites] * Complete either the [Authorization Code](/docs/issuance/authorization-code/tutorial) or [Pre-authorized Code](/docs/issuance/pre-authorized-code/tutorial) tutorials to configure a credential issuance flow. * Node.js 18+ installed locally. * To test locally you will need to expose your local consumer app to the internet. You can do this by setting up a [free ngrok account](https://ngrok.com/), using a Cloudflare tunnel or using your own solution. For this tutorial we will be using ngrok. Sign up for a free account at [ngrok.com](https://ngrok.com/). ## Tutorial steps [#tutorial-steps] ### Create a MATTR VII Webhook [#create-a-mattr-vii-webhook] 1. Log in to the [MATTR Portal](/docs/platform-management/portal) and navigate to the **Webhooks** section under **Platform Management**. 2. Click on **Create new**. 3. Use the *Event* type checkbox to select **Open ID credential issued**. This event is triggered upon completion of an [OID4VCI workflow](/docs/issuance/oid4vci-overview). 4. In the *URL* field, enter `https://example.com`. This is the URL that will receive the Webhook events data payload when they are triggered by MATTR VII for the specified events. We will update this to your webhook receiver sample app URL later. 5. Click on **Create** to create the Webhook. 6. Copy the *ID* value. You will need it to configure the webhook receiver sample app. Make a request of the following structure to [create a Webhook](/docs/platform-management/webhooks-api-reference#create-a-webhook): ```http title="Request" POST /v1/webhooks ``` ```json title="Request body" { "events": ["OpenIdCredentialIssued"], "url": "https://example.com" } ``` * `events` : This array includes the event types that will trigger this Webhook. The `OpenIdCredentialIssued` event is triggered upon completion of an [OID4VCI workflow](/docs/issuance/oid4vci-overview). * `url` : This is the URL that will receive the Webhook events data payload when they are triggered by MATTR VII for the specified events. We will update this to your actual webhook consumer URL later. *Response* ```json title="Response body" { "id": "0c099611-19c4-4f29-8724-6b9e5ba1ef7c", // [!code focus] "events": ["OpenIdCredentialIssued"], "url": "https://example.com", "disabled": false // } ``` * `id` : Unique identifier for the created Webhook. You will need it to configure the webhook receiver sample app. ### Set up the Webhook receiver sample app [#set-up-the-webhook-receiver-sample-app] 1. Clone the [MATTR Sample Apps repo](https://github.com/mattrglobal/sample-apps) to your machine and navigate to the `webhook-app` folder. 2. Rename the `env-example` file to `.env`. 3. Open the `.env` file and set the following environment variables: * `MATTR_TENANT_URL` : Your MATTR VII tenant URL. * `MATTR_WEBHOOK_ID` : The Webhook ID you copied when creating the Webhook in the previous step. * `NGROK_AUTHTOKEN` : Your ngrok authentication token. * `PORT` : Port for the webhook server (optional, defaults to 3000). 4. Install and start the app: ```bash npm install npm run dev ``` Or using Docker: ```bash docker compose up --build ``` You should see output indicating that the server is running and ngrok is set up, as shown in the following image: Webhook app running 5. Copy the `Public Webhook URL` from the ngrok output (e.g., `https://5d81c4374916.ngrok-free.app/webhook`). You will use it to update the Webhook URL in MATTR VII. The sample app core logic is within the `/src/index.js` file. You can review annotations in the sample app code to understand how it: * Fetches MATTR VII's public keys to verify incoming requests. * Verifies the request signature. * Logs the received event payload to the console. ### Update the Webhook URL in MATTR VII [#update-the-webhook-url-in-mattr-vii] 1. Log in to the [MATTR Portal](/docs/platform-management/portal) and navigate to the **Webhooks** section under **Platform Management**. 2. Select the Webhook you created earlier to open its details. 3. In the *URL* field, enter the `Public Webhook URL` you copied from the ngrok output in the previous step. 4. Click on **Save** to apply changes. Make a request of the following structure to [create a Webhook](/docs/platform-management/webhooks-api-reference#create-a-webhook): ```http title="Request" PUT /v1/webhooks/{id} ``` * `id` : The unique identifier of the Webhook you created earlier. ```json title="Request body" { "events": ["OpenIdCredentialIssued"], "url": "https://example.com" } ``` * `url` : Enter the `Public Webhook URL` you copied from the ngrok output in the previous step. ### Test the workflow [#test-the-workflow] 1. Use the MATTR GO example app to claim the credential offer you created in the [Authorization Code](/docs/issuance/authorization-code/tutorial) or [Pre-authorized Code](/docs/issuance/pre-authorized-code/tutorial) tutorials. 2. When the credential is issued, MATTR VII will trigger the webhook. 3. You should see the received event payload in the terminal window where your sample app webhook receiver is running. You can refer to the [Webhooks guide](/docs/platform-management/webhooks-guide#event-payload) for a detailed structure of the events payload. ## What’s next? [#whats-next] Now that you have a working webhook consumer, you can extend the application to perform more meaningful actions based on the received events, as opposed to merely logging them. For example, you could: * Store credential issuance events in a database. * Trigger downstream APIs for automated workflows. ### Production considerations [#production-considerations] When moving your Webhooks usage into production, consider the following best practices: * **Secure your Webhook endpoint:** * Require HTTPS at all times. * Use a firewall or allowlist to limit inbound traffic. * Validate that requests are signed with MATTR VII’s public keys (as shown in this tutorial). * **Make event processing idempotent:** * MATTR VII does not guarantee that events arrive in order or only once. * Deduplicate by checking the `event.id` or `deliveryId` in the payload before processing. * Ensure that reprocessing the same event has no negative effect (e.g., sending duplicate emails, double-updating a record). * **Add monitoring and logging:** * Capture request/response logs for debugging and auditing. * Monitor webhook failures, retries, and timeouts. * Consider alerting if repeated failures occur. # Android app signing URL: /docs/verification/android-app-signing When embedding remote verification capabilities into a mobile application, it's important to ensure that only trusted applications can make verification requests to your MATTR VII verifier tenant. For Android applications specifically, this is done by configuring the `packageSigningCertificateThumbprints` property when creating the [MATTR VII Verifier Application](/docs/verification/remote-verification-api-reference/verifier-applications#create-a-verifier-application). Every Android app must be signed with a certificate before it can be installed. This signing certificate identifies the developer or organization responsible for the app and is used by Android to verify authenticity and integrity. When integrating with MATTR verification capabilities, the package signing certificate thumbprint acts as a unique cryptographic identifier for your app's signing key. By verifying this thumbprint, the Verifier can confirm that the incoming request originated from a trusted and unmodified app. If the thumbprint doesn't match exactly, the Verifier will reject the request. This ensures that only trusted client applications — those signed with a matching certificate — can initiate verification requests. The purpose of this page is to explain how to obtain the signing certificate thumbprint for your Android app. ## What is a Package Signing Certificate Thumbprint? [#what-is-a-package-signing-certificate-thumbprint] A package signing certificate thumbprint is the hex-encoded SHA-256 hash of your app's signing certificate (the X.509 certificate bytes). It uniquely identifies the certificate used to sign your app and remains the same across app updates as long as the signing certificate does not change. ## Obtaining the Thumbprint [#obtaining-the-thumbprint] ### Production builds [#production-builds] When your app is ready for release, the thumbprint you configure must match the signing certificate used for your production build. Obtaining the thumbprint differs based on how you manage your signing keys - via Google Play App Signing or manually. #### Google Play App Signing [#google-play-app-signing] If your app uses Play App Signing, Google manages your app's signing key. You can find the SHA-256 fingerprint in the Google Play Console: 1. Go to **Google Play Console** > **Setup** > **App Signing**. 2. Locate the SHA-256 fingerprint under **App Signing Key Certificate**. Google Play Console 3. Copy the SHA-256 value. 4. Remove all `:` characters and convert the string to lowercase. ```js title="Example conversion" const fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5:12:34:56:78:9A:BC:DE:F0:12:34:56:78'; // Remove colons and convert to lowercase const sha256Hex = fingerprint.replaceAll(":", "").toLowerCase(); console.log(sha256Hex) ``` 5. Upload the processed value as your `packageSigningCertificateThumbprints` configuration. For more information, refer to Google's documentation on [Play App Signing Overview](https://developer.android.com/studio/publish/app-signing) and [Manage App Signing Keys in Google Play Console](https://support.google.com/googleplay/android-developer/answer/9842756?hl=en). #### Manual signing (CLI) [#manual-signing-cli] 1. Retrieve the SHA-256 fingerprint using the `keytool` command for the specific signing key alias: ```bash title="Command to get SHA-256 fingerprint" keytool -list -v -keystore -alias ``` 2. Copy the SHA-256 value. 3. Remove all `:` characters and convert the string to lowercase. ```js title="Example conversion" const fingerprint = '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5:12:34:56:78:9A:BC:DE:F0:12:34:56:78:9A:BC:DE:F0:12:34:56:78:9A:BC:DE:F0:12:34:56:78:9A:BC:DE:F0'; const sha256Hex = fingerprint.replaceAll(":", "").toLowerCase(); console.log(sha256Hex) ``` 4. Upload the processed value as your `packageSigningCertificateThumbprints` configuration. ### Local builds (Debug) [#local-builds-debug] 1. Extract the signing certificate information directly from the `.apk` file using the apksigner tool: ```bash title="Command to get SHA-256 fingerprint from APK" apksigner verify --print-certs path/to/your-debug-apk.apk ``` ```nginx title="Example output" Signer #1 certificate DN: C=US, O=Android, CN=Android Debug Signer #1 certificate SHA-256 digest: f59105881315e61502274a499d6efc2d7cc71c5cae266e598290d36b59221f6d // [!code focus] Signer #1 certificate SHA-1 digest: ca09773016ef4db66344ce0dac2827429ea875f1 Signer #1 certificate MD5 digest: c59905769e42c09530898c6dc413258f ``` 2. Copy the SHA-256 digest and upload it as your `packageSigningCertificateThumbprints` configuration. The default debug keystore is usually located at: `$HOME/.android/debug.keystore`. For more details, refer to [Android Debug Signing](https://developer.android.com/studio/publish/app-signing#debug-mode). ## Best practices [#best-practices] * If your app's signing key changes, you'll need to update your application configuration. * Consider removing old thumbprints if you wish to **invalidate older app versions** — for example, after a key rotation or potential compromise. * For development and testing, you can use the debug signing certificate thumbprint, but ensure to switch to the release signing certificate for production builds. * Always keep your signing keys secure and avoid sharing them publicly. # Choose your verification channel URL: /docs/verification/choosing-a-channel The first decision is where and how your users will present their credential. This page compares the three channels so you can pick the one that fits your use case. ## Choose your verification channel [#choose-your-verification-channel] The first decision is where and how your users will present their credential. Each channel has different technical requirements, user experience characteristics, and standards underpinnings. ### In-person verification (ISO/IEC 18013-5) [#in-person-verification-isoiec-18013-5] Best for: retail counters, border control, event entry, age verification at point of sale. The holder presents their credential by displaying a QR code or initiating a BLE connection from their wallet. Your verifier application reads the credential over a proximity channel, validates it cryptographically, and returns the result, all within seconds. * Uses Bluetooth Low Energy (BLE) for secure device-to-device communication. * Supports offline verification. * Aligned with [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html). **MATTR tooling:** [MATTR Pi Verifier SDKs](/docs/verification/sdks/overview) for iOS, Android, and React Native. See the [in-person verification overview](/docs/verification/in-person-overview) and [quickstart](/docs/verification/in-person-quickstart) to get started. ### Remote web verification (ISO/IEC 18013-7 + OID4VP) [#remote-web-verification-isoiec-18013-7--oid4vp] Best for: online account opening, e-commerce age gates, government service portals, insurance applications. Your web application initiates a verification request. The user's wallet receives the request (via redirect or the Digital Credentials API), the user consents to share specific attributes, and the verified result is returned to your backend. * Supports same-device and cross-device flows. * Uses [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) as the presentation protocol. * Aligned with [ISO/IEC 18013-7](https://www.iso.org/standard/91154.html). **MATTR tooling:** [MATTR Pi Verifier Web SDK](/docs/verification/remote-web-verifiers/sdks/overview) + [MATTR VII](/docs/digital-trust-service) as the backend verification service. See the [remote web verification workflow](/docs/verification/remote-web-verifiers/workflow) and [quickstart](/docs/verification/remote-web-verifiers/quickstart) to get started. ### Remote mobile verification (ISO/IEC 18013-7 + OID4VP) [#remote-mobile-verification-isoiec-18013-7--oid4vp] Best for: native mobile apps for banking, ride-sharing, age-restricted delivery, telehealth. Your native app triggers a verification request using the embedded Verifier SDK. The wallet app on the same device (or a secondary device) responds with the credential presentation. * Direct app-to-wallet communication on the same device. * Supports cross-device scenarios where the wallet is on a different device. * Uses [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) for interoperable credential exchange. **MATTR tooling:** [MATTR Pi Verifier Mobile SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) + [MATTR VII](/docs/digital-trust-service) as the backend verification service. See the [remote mobile verification workflow](/docs/verification/remote-mobile-verifiers/workflow) and [quickstart](/docs/verification/remote-mobile-verifiers/quickstart) to get started. ## Next steps [#next-steps] Once you have chosen a channel, [decide what data you need to verify](/docs/verification/deciding-what-to-verify). # Decide what data you need to verify URL: /docs/verification/deciding-what-to-verify mDoc verification supports selective disclosure, so you only request the specific data elements relevant to your use case. This page covers common scenarios and how presentation requests work. ## Decide what data you need to verify [#decide-what-data-you-need-to-verify] mDoc verification supports selective disclosure, meaning you only request the specific data elements relevant to your use case. Common verification scenarios include: | Use case | Typical data requested | | ----------------------------------- | ----------------------------------------------------- | | Age verification (alcohol, tobacco) | `age_over_18` or `age_over_21` | | Identity proofing (account opening) | `family_name`, `given_name`, `birth_date`, `portrait` | | Address verification | `resident_address`, `resident_city`, `resident_state` | | Document validity | `expiry_date`, `document_number`, `issue_date` | | Vehicle category check (mDL) | `driving_privileges` | Only request the minimum data elements needed for your business process. Selective disclosure is a core privacy feature of the mDoc standards and helps your application comply with data minimization requirements. When configuring your verifier, you define a **presentation request** that specifies which data elements to request from the credential. For mDLs, presentation requests map to the ISO/IEC 18013-5 namespace (`org.iso.18013.5.1`). Other mDoc credentials use the namespaces defined by their respective specifications (for example, ISO/IEC 23220-based credentials). Both MATTR VII and the MATTR Pi SDKs support flexible presentation requests across these namespaces. ## Next steps [#next-steps] With your data requirements defined, [embed the SDKs into your application](/docs/verification/embedding-the-sdks). # Embed the SDKs into your application URL: /docs/verification/embedding-the-sdks MATTR provides SDKs that abstract the protocol complexity so you can focus on your user experience. This page covers the recommended integration approach, the SDK options for each channel, and the backend configuration remote channels require. ## Embed the SDKs into your application [#embed-the-sdks-into-your-application] MATTR provides SDKs that abstract the protocol complexity so you can focus on your user experience. ### Integration approach [#integration-approach] The recommended approach is to embed the MATTR Pi Verifier SDK directly into your application rather than redirecting users to an intermediate verification service. This: * Maintains trust continuity. Wallets validate your app's origin directly. * Delivers a seamless user experience without navigation interruptions. * Ensures compatibility with platform-specific security requirements. See [implementation guidelines](/docs/verification/remote-verification-implementation-guidelines) for detailed guidance on direct integration. ### SDK options by channel [#sdk-options-by-channel] | Channel | SDK | Platforms | | ------------- | ---------------------------------------------------------------------------------------- | -------------------------- | | In-person | [MATTR Pi Verifier SDK](/docs/verification/sdks/overview) | iOS, Android, React Native | | Remote web | [MATTR Pi Verifier Web SDK](/docs/verification/remote-web-verifiers/sdks/overview) | Browser (JavaScript) | | Remote mobile | [MATTR Pi Verifier Mobile SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) | iOS, Android, React Native | ### Backend configuration (remote channels) [#backend-configuration-remote-channels] For remote verification (web and mobile), you also need a MATTR VII tenant configured with: * A **verifier application** registered with your domain or app identifiers. * A **wallet provider** configuration for each wallet ecosystem you accept. * **Trusted issuer certificates** for the jurisdictions and credential types you support. ## Next steps [#next-steps] Next, [establish trust with issuers](/docs/verification/establishing-trust) so your application recognizes the credentials it receives. # Establish trust with issuers URL: /docs/verification/establishing-trust For mDoc verification to work, your application must trust the issuer's signing certificates. This page explains how trust is established, how MATTR's managed trust lists reduce your operational burden, and how to configure trusted issuers. For the broader picture of how trust lists work across a network (trusted issuers, trusted readers, and trusted wallets), see [Trusted Lists](/docs/digital-trust-service/trusted-lists) in the Digital Trust Service section. ## Establish trust with issuers [#establish-trust-with-issuers] For mDoc verification to work, your application must trust the issuer's signing certificates. The underlying [chain of trust](/docs/concepts/chain-of-trust) model is the same across mDoc credential types, though the terminology differs by credential family. ### How trust works [#how-trust-works] 1. Each issuer publishes root certificates. For mDLs, these are **Issuing Authority Certificate Authority (IACA)** roots, published at the state, territory, or national level depending on the jurisdiction. Other mDoc credential types follow the same chain-of-trust model with their own issuer certificate authorities. 2. Your verifier is configured with a list of trusted root certificates. 3. During verification, the SDK validates that the credential's signature chains back to a trusted root. 4. If the chain is valid, the credential is trusted. If not, verification fails. ### Managed trust lists from MATTR [#managed-trust-lists-from-mattr] Maintaining an up-to-date list of issuer certificates across multiple jurisdictions and credential types is operationally complex. Certificates rotate, new issuers come online, and different authorities may publish certificates independently. MATTR provides **managed trust lists** that include verified IACA certificates for mDL issuers: * **Australia**: all state and territory issuers. * **United States**: state-level issuers participating in mDL programs. * Additional jurisdictions as they come online. These managed lists are kept current by MATTR, reducing your operational burden and ensuring your application stays compatible as new issuers join the ecosystem. For non-mDL mDoc credentials, you configure the issuer's certificates directly through the same trust APIs. For in-person verification, trusted issuer certificates are configured locally in the SDK. For remote verification, they are managed through the [MATTR VII Trusted Issuers API](/docs/verification/remote-verification-api-reference/trusted-issuers). ### Configuring trusted issuers [#configuring-trusted-issuers] * **In-person (SDK):** Load trusted root certificates into the SDK's local trust store. * **Remote (MATTR VII):** Use the MATTR Portal or the [Create a Trusted Issuer](/docs/verification/remote-verification-api-reference/trusted-issuers#create-a-trusted-issuer) API to register issuer certificates on your tenant. For deeper guidance on certificates and the chain of trust used in verification, see the [certificates](/docs/verification/certificates/overview) pages under Establishing trust. ## Next steps [#next-steps] Once trust is established, learn about [handling verification results](/docs/verification/handling-results). # Frequently asked questions URL: /docs/verification/faq Answers to common questions about mDoc verification with MATTR. For the concepts behind these answers, see the [verification overview](/docs/verification). ## Frequently asked questions [#frequently-asked-questions] ### What standards does mDoc verification use? [#what-standards-does-mdoc-verification-use] mDoc verification is based on [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) for in-person (proximity) verification and [ISO/IEC 18013-7](https://www.iso.org/standard/91154.html) for remote (online) verification. Remote verification uses [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) as the presentation protocol, as specified in ISO/IEC 18013-7 Annex B. For mDoc credentials beyond the driver's license, [ISO/IEC 23220](/docs/concepts/iso-mdoc-standards) generalizes these foundations. ### Can I verify credentials offline? [#can-i-verify-credentials-offline] Yes. In-person verification using the MATTR Pi Verifier SDK operates over BLE and can perform full cryptographic verification without network connectivity. Trusted issuer certificates and revocation status lists are cached locally. Remote verification requires network connectivity. ### Which wallets are supported? [#which-wallets-are-supported] MATTR's verification SDKs are interoperable with any wallet that implements ISO/IEC 18013-5 (for proximity) or ISO/IEC 18013-7 with OID4VP (for remote). This includes Apple Wallet, Google Wallet, and government-issued wallet applications. Specific wallet support is configured through [wallet provider registrations](/docs/verification/remote-verification-api-reference/wallet-providers) in MATTR VII. ### How do I handle expired or revoked credentials? [#how-do-i-handle-expired-or-revoked-credentials] The verification result includes checks for credential validity period and revocation status. Your application should treat expired or revoked credentials as failed verifications and prompt the user to present a current credential. See [revocation status checks](/docs/verification/in-person-guides/revocation-status-check) for configuration details. ### What if I need to support multiple jurisdictions? [#what-if-i-need-to-support-multiple-jurisdictions] Configure your trusted issuers list with root certificates from each jurisdiction you want to accept. MATTR's managed trust lists simplify this for mDLs by providing pre-verified certificate bundles for supported regions. For other mDoc credential types, register the issuer's certificates through the same trust APIs. [Contact us](mailto:dev-support@mattr.global) to discuss your jurisdiction requirements. ### Do I need both MATTR Pi and MATTR VII? [#do-i-need-both-mattr-pi-and-mattr-vii] It depends on your channel: * **In-person only:** MATTR Pi Verifier SDK is sufficient for proximity-based verification. * **Remote (web or mobile):** You need both MATTR Pi (Verifier SDK embedded in your app) and MATTR VII (backend service that manages trust, sessions, and verification logic). ### How long does verification take? [#how-long-does-verification-take] In-person verification typically completes in 1-3 seconds. Remote verification depends on the user's interaction speed with their wallet, but the cryptographic verification itself is near-instant once the presentation is received. ### What data do I receive after verification? [#what-data-do-i-receive-after-verification] You receive only the data elements you requested in your presentation definition. The holder must consent to share each element. You do not receive the full credential or any data beyond what was explicitly requested. # Handling verification results URL: /docs/verification/handling-results When verification completes, your application receives a structured result. This page describes what the result contains and how to act on both success and failure paths. ## Handling verification results [#handling-verification-results] When verification completes, your application receives a structured result containing: * **Verification status**: Whether the credential passed all checks. * **Disclosed claims**: The specific data elements the holder consented to share. * **Verification checks performed**: Signature validity, issuer trust, expiry, revocation status. * **Failure details**: If verification failed, the specific reason. Your application logic should handle both success and failure paths gracefully. For detailed guidance on interpreting and acting on results, see: * [Handling in-person verification results](/docs/verification/in-person-guides/handling-verification-results) * [Handling remote web verification results](/docs/verification/remote-web-verifiers/guides/handling-verification-results) ## Next steps [#next-steps] Review the [frequently asked questions](/docs/verification/faq) for answers to common verification questions. # In-person verification URL: /docs/verification/in-person-overview In-person verification is a process where a credential holder presents their credential directly to a verifier in a proximity-based interaction. This may involve showing the credential to a person using a verifier device or to a self-service kiosk. Engagement methods include scanning a QR code, using a secure reader, or leveraging other proximity technologies. In-person verification is available for the following credential formats: * [mDocs](#mdocs) * [CWT credentials (including Semantic CWT)](#cwt-credentials) ## mDocs [#mdocs] ### Overview [#overview] mDocs can be verified in-person using proximity based technologies such as Bluetooth Low Energy (BLE), and support offline verification, as defined in [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html). Refer to the [proximity verification overview](/docs/verification/in-person-overview) for more information. ### Verification flow [#verification-flow] Proximity (in-person) verification comprises two phases: 1. [**Engagement phase**](#engagement-phase): The interaction begins with the two devices attempting to establish a secure communication channel for transferring data. In the context of mDocs, this is between the holder’s and the verifier’s devices. 2. [**Data retrieval phase**](#data-retrieval-phase): Once a secure communication channel is established, data can be securely requested and exchanged between the two devices. For mDocs, this would be exchanging various data objects required for the verification workflow. #### Engagement phase [#engagement-phase] The engagement phase requires two mobile devices to: * Become aware of one another: Enabling the holder’s device to explicitly share engagement information with a verifier's device they wish to interact with. * Establish a secure communication channel: As wireless technologies are essentially insecure, additional security layers are required to prevent third parties from eavesdropping transactions. The [ISO/IEC 18013-5:2021 standard](https://www.iso.org/standard/69084.html) defines this layer, and it is quite similar to the usage of Transport Layer Security (TLS) for internet based protocols. Engagement phase ##### Holder generates a QR code [#holder-generates-a-qr-code] This phase is always initiated by the holder, who generates a QR code to be read by the verifier. While it is true that any device can read this QR code, the assumption is that the holder will only show the QR code to a verifier they trust and wish to share information with. The information in the QR code details: * Wireless communication protocols supported by the holder. * How the verifier can connect with the holder. * Ephemeral session public key. * Additional feature negotiations. ##### Verifier returns session key [#verifier-returns-session-key] Assuming the verifier device supports the same protocol requirements, it will generate its own ephemeral session public key, and attempt to establish a secure connection via a hand-shake response. The verifier response also includes an encrypted presentation request. However, for clarity purposes this step is described as part of the data retrieval phase. ##### Secure communication established [#secure-communication-established] As the two devices connect, a unique session transcript is created and used alongside the holder and verifier keys to derive a unique session key that is used to encrypt any exchanged data. This session key is unique for every session and removed when the session is terminated. Any attempt to use the same session key in a different session would fail, even if the session is between the same two devices. The holder will use the session key to decrypt the message (e.g. request from the verifier) and encrypt the responses (e.g. shared mDocs). #### Data retrieval phase [#data-retrieval-phase] Once a secure connection is established, the following flow takes place: Data phase ##### Request [#request] The verifier sends a presentation request. This details what type of information they wish to verify. Note that the encrypted presentation request is actually sent alongside the verifier ephemeral public key during the last step of the engagement phase. However, for clarity purposes this step is described as part of the data retrieval phase. ##### Consent [#consent] The holder receives the request and asks for the user’s consent to share the information. When applying selective disclosure, the holder device should show the user which of their mDocs include the required information, and enable the user to select which one to share. ##### Response [#response] When the user agrees to share the information, a presentation is sent back to the verifier with all of the information the user has consented to share. ##### Verification [#verification] The verifier will check the presentation, its signature and its issuer to complete the verification workflow with a verified/rejected conclusion. ### Verification checks [#verification-checks] The following standard checks are performed on all mDocs verification requests: * Issuer IACA is valid as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Credential was issued by a trusted issuer (by checking the issuer's IACA against a [local](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:trusted-issuers) or [external](/docs/digital-trust-service) list of trusted issuers). * Digital signature is valid. * Credential structure complies with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). The following checks are optional and defined as part of the verification request: * Current time is after the beginning of the credential validity period. * Current time is not after the end of the credential validity period. * Credential has not been revoked. ### Security considerations [#security-considerations] The mDocs communication protocol has a further control to mitigate replay attacks, where a credential is presented to an incorrect verifier or presented multiple times. Device and session binding prohibit this by ensuring that a middle man hasn't poached the credential during transmission and that the credential is only fresh and valid for the intended transaction. ## CWT credentials [#cwt-credentials] ### Overview [#overview-1] CWT and Semantic CWT credentials are verified via Direct verification. The holder physically presents the credential to a verifying device, which makes a direct API request to a MATTR VII endpoint with the credential enclosed in the request body. The endpoint then verifies the presented presented CWT or Semantic CWT credential and returns the verification result in the response. ### Verification flow [#verification-flow-1] CWT and Semantic CWT credentials can be provided for verification in one of two formats: * Signed credential encoded as a string. This will be the encoded element of the credential issuance response. * Signed credential encoded as a QR code and represented as a PDF document or an image file with the following limitations: * File size must be 1MB or under. Larger files are rejected with a `413` error. * Only the first page of PDF documents is processed. * Image files must contain a QR code of sufficient quality and resolution. This depends on many factors such as the size of the QR relative to the image, and whether the image was processed in any way. * For optimal performance, ensure that only a single QR code is present on the file. ### Verification checks [#verification-checks-1] The following standard checks are performed on all CWT or Semantic CWT credentials verification requests: * Conformance of the string and encoded data. * All string representations of CWT credentials must be prefixed with CSC/1. * All string representations of Semantic CWT credentials must be prefixed with CSS/1. * Decoded payload structure is a valid CWT or Semantic CWT credential. * Issuer DID can be used to resolve its DID document. * Public key from issuer's DID document validates the proof signature, confirming the credential has not been tampered with. The following checks are optional and are defined as part of the verification request: * Credential was issued by a trusted issuer. * Current time is after the beginning of the credential validity period. * Current time is not after the end of the credential validity period. * Credential has not been revoked. # In-person verification quickstart guide URL: /docs/verification/in-person-quickstart This quickstart is for evaluating the [MATTR Pi mDocs Verifier SDKs](/docs/verification/sdks/overview) for in-person verification. In about 15-20 minutes you will configure a sample verifier mobile app (iOS, Android, or React Native), run it on a test device and use it to verify an mDoc presented in-person from a different device using the [proximity presentation workflow](/docs/verification/in-person-overview) defined by [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). Use this guide as a fast evaluation path to see the Verifier Mobile SDK working end-to-end on real devices. For detailed information and API examples, explore the [tutorial](/docs/verification/in-person-tutorial) and reference documentation tailored for each platform. ## User experience [#user-experience] In this quickstart you will perform this exact flow yourself using the sample verifier app and the GO Hold example app: Tutorial Workflow 1. The holder uses their wallet application to generate and present a QR code. 2. The verifier scans the QR code, connects with the wallet, and requests an mDoc for verification. 3. The wallet application displays matching credentials to the holder and asks for consent to share them with the verifier. 4. The verifier application receives the wallet's response and verifies the provided credential. 5. Verification results are displayed to the verifier. ## Prerequisites [#prerequisites] * Use the [Get Started form](/docs/resources/get-started) to request a trial of MATTR **verification** capabilities. You will receive access to the following resources: * MATTR Pi mDocs Verifier SDK for your chosen platform (iOS, Android, or React Native). * MATTR VII tenant. * Two physical mobile devices to run the verifier and holder applications: * Verifier device: iOS/Android mobile device to run the built Verifier application on, set up with Bluetooth access. * Holder device: iOS/Android mobile device with the **MATTR GO Hold example app** installed for [iOS](https://apps.apple.com/app/mattr-wallet/id1518660243) or [Android](https://play.google.com/store/apps/details?id=global.mattr.wallet), set up with biometric authentication and Bluetooth access. * Development environment set up for your chosen platform (e.g., Xcode for iOS, Android Studio for Android, or a React Native development environment). ## Steps [#steps] In this quickstart you will: 1. Configure a local sample verifier project for your platform. 2. Run a sample verifier app and verify a credential presented in-person from a holder device. 3. Review the structure of the sample project so you can see how to integrate the SDK into your own app. Use the tabs below to follow platform-specific setup steps. ### Create a MATTR VII backend tenant (1-3 Minutes) [#create-a-mattr-vii-backend-tenant-1-3-minutes] The iOS and Android Verifier SDKs require a MATTR VII backend tenant. If you already have a tenant, or are evaluating the React Native SDK, you can skip this step. 1. Log into the [MATTR Portal](https://portal.mattr.global). 2. Select the **Create/switch tenant** button on the top-right side of the screen.\ The *All tenants* panel is displayed, listing any existing tenants. 3. Select the **Create new** button.\ The *New tenant* form is displayed. 4. Use the *Region* dropdown list to select the region your tenant will be hosted in. 5. Use the *Tenant subdomain* text box to insert a subdomain for your tenant (e.g. `in-person-verification`). 6. Select the **Create** button to create the new tenant. 7. Copy the displayed tenant information (`audience`, `auth_url`, `tenant_url`, `client_id` and `client_secret`) which is required for the next step. ### Create a MATTR VII Verifier Application (3-5 Minutes) [#create-a-mattr-vii-verifier-application-3-5-minutes] If you are evaluating the iOS or Android Verifier SDK, you must create a Verifier Application on your MATTR VII tenant. If you are evaluating the React Native Verifier SDK, you do not need to create a Verifier Application and can skip this step. [Make a request](/docs/api-reference#getting-started) of the following structure to create an iOS Verifier Application configuration on your MATTR VII tenant **(make sure to replace `bundleId` and `teamId` with your own values)**: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `bundleId`: This must match the bundle identifier of your iOS application you will create in the next step. * `teamId`: This must match the Apple Developer Team ID that actually signs this app. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", // [!code focus] "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. [Make a request](/docs/api-reference#getting-started) of the following structure to create an Android Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false }, "openid4vpConfiguration": { "redirectUri": "com.yourcompany.verifierapp://oid4vp-callback" } } ``` A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", // [!code focus] "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. This step is currently not required for the React Native Verifier SDK. ### Configure the sample verifier project (5-10 Minutes) [#configure-the-sample-verifier-project-5-10-minutes] 1. Access the sample verifier codebase by either: * Cloning the [MATTR Sample Apps repository](https://github.com/mattrglobal/sample-apps): ```bash title="Clone sample apps" git clone https://github.com/mattrglobal/sample-apps.git ``` * Or downloading just the iOS sample verifier project using [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Fios-in-person-verifier-tutorial-sample-app). 2. Use Xcode to open the `.xcodeproj` file in the project's folder. You can find it in the `sample-apps/ios-in-person-verifier-tutorial-sample-app` directory. 3. Drag the `MobileCredentialVerifierSDK.xcframework` folder (obtained from MATTR as part of the SDK package) into your project. 4. Configure `MobileCredentialVerifierSDK.xcframework` to [Embed & sign](https://help.apple.com/xcode/mac/current/#/dev51a648b07). 5. Set a unique bundle identifier for the project in the Xcode project settings (e.g., `com.yourname.proximityverifier`). This must match the `bundleId` you used when creating the Verifier Application in the [Create a MATTR VII Verifier Application](#create-a-mattr-vii-verifier-application-3-5-minutes) step. 6. Open the `Constants.swift` file and update it with your tenant and application details: ```swift title="Constants.swift" static let tenantHost = URL(string: "https://your-tenant.vii.mattr.global")! static let applicationId = "" ``` * `tenantHost`: Replace with the URL of your MATTR VII tenant, available in the MATTR Portal under **Platform Management > Tenant**. * `applicationId`: Replace with the `id` returned when you created the Verifier Application in the [Create a MATTR VII Verifier Application](#create-a-mattr-vii-verifier-application-3-5-minutes) step. 7. Run the project on a physical iOS device (simulators do not support the required Bluetooth capabilities). 1. Access the sample verifier codebase by either: * Cloning the [MATTR Sample Apps repository](https://github.com/mattrglobal/sample-apps): ```bash title="Clone sample apps" git clone https://github.com/mattrglobal/sample-apps.git ``` * Or downloading just the Android sample verifier project using [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Fandroid-in-person-verifier-tutorial-sample-app). 2. Open the project in Android Studio. You can find it in the `sample-apps/android-in-person-verifier-tutorial-sample-app` directory. 3. Unzip the `mobile-credential-verifier-*version*.zip` file (obtained from MATTR as part of the SDK package). 4. Drag the unzipped `global` folder into the project's `repo` folder. 5. Sync Project with Gradle files to recognize the new module. 6. Open the `Constants.kt` file and update it with your tenant and application details: ```kotlin title="Constants.kt" const val TENANT_HOST = "https://your-tenant.vii.mattr.global" const val APPLICATION_ID = "" ``` * `TENANT_HOST`: Replace with the URL of your MATTR VII tenant, available in the MATTR Portal under **Platform Management > Tenant**. * `APPLICATION_ID`: Replace with the `id` returned when you created the Verifier Application in the [Create a MATTR VII Verifier Application](#create-a-mattr-vii-verifier-application-3-5-minutes) step. 7. Run the project on a physical Android device (emulators do not support the required Bluetooth capabilities). 8. Retrieve your app's signing certificate thumbprint and convert it to the required format: * Open a terminal inside your project's root folder and run: ```bash title="Retrieve signing certificate" ./gradlew signingReport ``` If you see `permission denied: ./gradlew`, the Gradle wrapper has lost its executable bit (this happens when the project is downloaded as a zip rather than cloned). Make it executable and run the command again: ```bash title="Fix wrapper permissions" chmod +x gradlew ``` * Locate the `SHA-256` value in the `Variant: debug` section, remove all colons (`:`), and convert all letters to lowercase. The result is the thumbprint you will send to your tenant in the next step: ```bash title="Example conversion" echo '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5' | tr -d ':' | tr 'A-F' 'a-f' ``` This thumbprint is only valid for your debug builds. If you intend to publish your app, repeat this process with the release signing certificate. Refer to [Android App Signing](/docs/verification/android-app-signing) for more information. 9. Update your Verifier Application with the formatted thumbprint by making a request of the following structure. Use the application `id` returned in the [Create a MATTR VII Verifier Application](#create-a-mattr-vii-verifier-application-3-5-minutes) step as the `{applicationId}` path parameter, and set `packageSigningCertificateThumbprints` to the thumbprint you generated in the previous step: ```http title="Request" PUT /v2/presentations/applications/{applicationId} ``` ```json title="Request body" { "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "91f7cbf9d681531bc7a58fb833cca14dabede509c5" ], "keyAttestation": { "required": false }, "openid4vpConfiguration": { "redirectUri": "com.yourcompany.verifierapp://oid4vp-callback" } } ``` The `PUT` request replaces the entire application configuration, so include all the fields you set when you created the application in the [Create a MATTR VII Verifier Application](#create-a-mattr-vii-verifier-application-3-5-minutes) step, not just the thumbprint. A successful response returns a `200` status code with the updated Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", "name": "My Android Verifier Application", "type": "android", "packageSigningCertificateThumbprints": [ "91f7cbf9d681531bc7a58fb833cca14dabede509c5" ], // ... rest of application configuration } ``` 1. Access the completed sample verifier codebase by either: * Cloning the MATTR sample-apps repository: ```bash title="Clone the repository" git clone https://github.com/mattrglobal/sample-apps.git ``` * Or downloading just the React Native sample verifier project using the [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Freact-native-in-person-verifier-tutorial%2Freact-native-in-person-verifier-tutorial-complete) utility. 2. Open the completed sample verifier project in your code editor. You can find it in the `sample-apps/react-native-in-person-verifier-tutorial/react-native-in-person-verifier-tutorial-complete` directory. 3. Open the `app.config.ts` file. 4. Update the iOS `bundleIdentifier` to a unique value for your application, e.g. `com.mycompany.myapp`. ```typescript title="app.config.ts" bundleIdentifier: "com.mycompany.myapp", ``` 5. Update the Android `package` to a unique value, e.g. `com.mycompany.myapp`: ```ts title="app.config.ts" package: "com.mycompany.myapp", ``` 6. Navigate to the starter project directory and install dependencies: ```bash title="Install dependencies" yarn install ``` You must be logged in to npm with access to the MATTR Pi mDocs Verifier SDK package to install dependencies successfully. If you have not yet been granted access to the SDK, apply for access [here](/docs/resources/get-started). 7. Run the following command to generate the iOS and Android project files: ```bash title="Generate project files" yarn expo prebuild ``` You should now see the `ios` and `android` folders in your project root. 8. Connect your testing device(s) and run the following command(s) to start the application(s): **iOS** ```bash title="Run iOS application" yarn ios --device ``` **Android** ```bash title="Run Android application" yarn android --device ``` ### Verify a credential using the sample verifier app (3 Minutes) [#verify-a-credential-using-the-sample-verifier-app-3-minutes] 1. On your **holder testing device**, launch the **MATTR GO Hold example app**. 2. Tap the Blue **Share** button. 3. Select **Respond or Collect**. This will open the camera view (You may need to allow the app to access your camera). 4. Scan the following QR code: QR Code 5. Follow the on-screen instructions to claim the credential (Note that this workflow requires an active internet connection). 6. Once the credential is claimed, select the Blue **Share** button again. 7. Select **Share Credential** and then choose the **Connection QR** tab to display a QR code. 8. On your **verifier testing device**, launch the sample verifier app and select the **Scan QR Code** button. 9. Use the **verifier testing device** to scan the QR code displayed on the **holder testing device**. 10. On your **holder testing device**, consent to sharing the information with the verifier. 11. On your **verifier testing device**, review the verification results displayed in the app. Behind the scenes, the Verifier Mobile SDK handles device engagement, secure session establishment, and verification of the mDoc according to ISO/IEC 18013-5:2021. ## Review the codebase [#review-the-codebase] The sample verifier application uses the [Verifier Mobile SDK](/docs/verification/sdks/overview) to verify mDocs presented in-person from a wallet. Once you have the sample application running, use this section to inspect the key SDK calls you will reuse in your own application. The sample projects include code comments that explain the purpose of each step and how it maps to the in-person verification workflow. The key steps for handling in-person verification with the SDK are outlined below, along with links to the relevant API reference documentation for each platform. ### Initialize the SDK [#initialize-the-sdk] In the sample app, SDK initialization happens once during startup so the verifier can handle in-person sessions. On iOS and Android, [SDK Tethering](/docs/verification/sdks/sdk-tethering) is required, so the SDK is initialized with a platform configuration that points at your MATTR VII tenant and Verifier Application. This registers the app instance and obtains a license on first launch. `initialize` is asynchronous and takes a `PlatformConfiguration`. It can throw `failedToRegister` and `invalidLicense` when tethering fails: ```swift title="Initialize the SDK" let platformConfiguration = PlatformConfiguration( tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, applicationId: "" ) try await mobileCredentialVerifier.initialize(platformConfiguration: platformConfiguration) ``` `initialize` takes a `PlatformConfiguration` and can throw `FailedToRegisterException` and `InvalidLicenseException` when tethering fails: ```kotlin title="Initialize the SDK" val platformConfiguration = PlatformConfiguration( tenantHost = URL("https://your-tenant.vii.mattr.global"), applicationId = "" ) MobileCredentialVerifier.initialize(activity, platformConfiguration) ``` The React Native Verifier SDK is not tethered, so initialization needs no platform configuration: ```tsx title="Initialize the SDK" const result = await initialize(); if (result.isErr()) { // Handle error: result.error } ``` ### Adding a trusted issuer certificate [#adding-a-trusted-issuer-certificate] In the sample app, a trusted issuer certificate is added during startup after SDK initialization. This allows the verifier to validate the authenticity of credentials issued by trusted issuers. ```swift title="Add a trusted issuer certificate" try await mobileCredentialVerifier.addTrustedIssuerCertificates(certificates: [certificate]) ``` * `certificate` : Pass the IACA certificate of an issuer you want your application to trust. ```kotlin title="Add a trusted issuer certificate" MobileCredentialVerifier.addTrustedIssuerCertificates(certificates = listOf(certificate)) ``` * `certificate` : Pass the IACA certificate of an issuer you want your application to trust. ```tsx title="Add a trusted issuer certificate" await mobileCredentialVerifier.addTrustedIssuerCertificates([certificate]) ``` * `certificate` : Pass the IACA certificate of an issuer you want your application to trust. ### Create a presentation request object [#create-a-presentation-request-object] Before sending a presentation request to the holder, the verifier application needs to create a request object that defines the type of credential and specific attributes being requested for verification. ```swift title="Create a presentation request" let mobileCredentialRequest = MobileCredentialRequest( docType: "", namespaces: [ "": [ "": false, "": false, "": false ] ] ) ``` * `docType` : The type of credential being requested (e.g., an mDL). * `namespaces` : A dictionary mapping each namespace to its requested attributes. Each attribute is a key with a Boolean indicating whether the verifier intends to retain this attribute (`true`) or not (`false`). ```kotlin title="Create a presentation request" val mobileCredentialRequest = MobileCredentialRequest( docType = "", namespaces = NameSpaces( mapOf( "" to DataElements( mapOf( "" to false, "" to false, "" to false ) ) ) ) ) ``` * `docType` : The type of credential being requested (e.g., an mDL). * `namespaces` : A map of requested attributes under each namespace, where false indicates the verifier does not intent to retain this attribute. ```tsx title="Create a presentation request" const mobileCredentialRequest = { docType: '', namespaces: { '': { '': false, '': false, '': false } } } ``` * `docType` : The type of credential being requested (e.g., an mDL). * `namespaces` : An object that defines the attributes being requested for each namespace. A value of `false` indicates the verifier does not intent to retain this attribute. ### Create a proximity presentation session [#create-a-proximity-presentation-session] Once the verifier application receives a device engagement string (e.g., from scanning a QR code presented by the holder), it can create a proximity presentation session to establish a secure connection with the holder's wallet and send presentation requests. 1. Extend a class with the [`ProximityPresentationSessionListener`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/proximitypresentationsessionlistener) protocol: ```swift title="Conform to ProximityPresentationSessionListener" extension VerifierViewModel: ProximityPresentationSessionListener { public func onEstablished() { // Handle session establishment: show presentation session details or send a request. } public func onTerminated(error: (any Error)?) { /// Handle session termination print("Session Terminated") } } ``` 2. Call the [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialverifier/createproximitypresentationsession\(encodeddeviceengagementstring:listener:\)) function with an instance of the extended class: ```swift title="Create a proximity presentation session" mobileCredentialVerifier.createProximityPresentationSession(encodedDeviceEngagementString: deviceEngagementString, listener: self) ``` * `encodedDeviceEngagementString` : Pass the device engagement string retrieved from a QR code presented by the holder. * `listener` : An object that conforms to `ProximityPresentationSessionListener` protocol and will handle the session events. 1. Implement the [`ProximityPresentationSessionListener`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-proximity-presentation-session-listener/index.html) interface: ```kotlin title="Implement ProximityPresentationSessionListener" val sessionListener = object : ProximityPresentationSessionListener { override fun onEstablished() { // Handle session establishment: show presentation session details or send a request. } override fun onTerminated(error: Throwable?) { Log.d("SessionListener", "Session Terminated") } } ``` 2. Call the [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-mobile-credential-verifier/create-proximity-presentation-session.html?query=fun%20createProximityPresentationSession\(activity:%20Activity,%20qrEncodedDeviceEngagementString:%20String,%20listener:%20ProximityPresentationSessionListener\)) function with an instance of the extended class: ```kotlin title="Create a proximity presentation session" MobileCredentialVerifier.createProximityPresentationSession(activity, deviceEngagementString, sessionListener) ``` * `deviceEngagementString` : Pass the device engagement string retrieved from a QR code presented by the holder. * `sessionListener` : An object that implements `ProximityPresentationSessionListener` interface and will handle the session events. ```tsx title="Create a proximity presentation session" const proximityPresentationSession = await mobileCredentialVerifier.createProximityPresentationSession({ encodedDeviceEngagementString: encodedDeviceEngagementString, onSessionTerminated: (error) => { // Handle session termination or error } }) ``` * `encodedDeviceEngagementString` : Pass the device engagement string retrieved from a QR code presented by the holder. * `onSessionTerminated` : A callback triggered when the session ends or fails. The [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/functions/createProximityPresentationSession.html) method returns an empty success response, indicating that the session has been created and the verifier application can send a presentation request to the holder. ### Listen to NFC session requests [#listen-to-nfc-session-requests] To support NFC-based device engagement in addition to QR code scanning, the verifier application can listen for NFC session requests. When an NFC session request is received, the SDK will automatically create a proximity presentation session and trigger the appropriate session listener callbacks. This capability is currently only available in the Android SDK. ```kotlin title="Start listening to NFC session requests" MobileCredentialVerifier.registerForNfcDeviceEngagement(activity, sessionListener) ``` * `sessionListener` : An object that implements the `ProximityPresentationSessionListener` interface and will handle the session events. ```kotlin title="Stop listening to NFC session requests" MobileCredentialVerifier.deregisterForNfcDeviceEngagement(activity) ``` This capability is currently only available in the Android SDK. ### Send a presentation request [#send-a-presentation-request] After a proximity presentation session is established (e.g., after scanning a QR code or receiving an NFC engagement), the verifier application can send a presentation request to the holder's wallet to request specific credentials for verification. ```swift title="Send a presentation request" try await mobileCredentialVerifier.sendProximityPresentationRequest(request: [mobileCredentialRequest], checkStatus: true) ``` * `request` : Pass the [`MobileCredentialRequest`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialrequest) object created in the previous step. * `checkStatus` : A Boolean indicating whether to check credential revocation status. Set to `true` (default) to check credential revocation status as part of the verification, or `false` to skip status checking. ```kotlin title="Send a presentation request" MobileCredentialVerifier.sendProximityPresentationRequest(request = listOf(mobileCredentialRequest), checkStatus = true) ``` * `request` : Pass the [`MobileCredentialRequest`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier.dto/-mobile-credential-request/index.html?query=data%20class%20MobileCredentialRequest\(val%20docType:%20DocType,%20val%20namespaces:%20NameSpaces\)) object created in the previous step. * `checkStatus` : A Boolean indicating whether to check credential revocation status. Set to `true` (default) to check credential revocation status as part of the verification, or `false` to skip status checking. ```tsx title="Send a presentation request" const mobileCredentialResponse = await proximityPresentationSession.sendProximityPresentationRequest({ request: mobileCredentialRequest, checkStatus: true }) ``` * `request` : Pass the [`MobileCredentialRequest`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/types/MobileCredentialRequest.html) object created two steps up. * `checkStatus` : A Boolean indicating whether to check credential revocation status. Set to `true` (default) to check credential revocation status as part of the verification, or `false` to skip status checking. ### Handle the presentation response [#handle-the-presentation-response] After sending a presentation request, the verifier application will receive a response from the holder's wallet containing the credentials that match the request along with their verification status. The application can then handle this response to display verification results to the user. The [`sendProximityPresentationRequest`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialverifier/sendproximitypresentationrequest\(request:checkstatus:\)) method returns a [`MobileCredentialResponse`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialresponse) object. This response contains the presentation details provided by the holder, including any errors encountered and the verification status of the credentials returned in the response: ```swift title="MobileCredentialResponse" let mobileCredentialResponse = MobileCredentialResponse( credentialErrors: [/* CredentialError */], credentials: [ MobileCredentialPresentation( branding: /* Branding information for displaying the credential */, claimErrors: [ /* Namespace */: [ /* ElementID */: /* MobileCredentialResponseErrorCode */ ] ], claims: [ /* Namespace */: [ /* ElementID */: /* MobileCredentialElementValue */ ] ], docType: /* DocType */, issuerInfo: /* IssuerInfo */, validityInfo: /* Validity */, verificationResult: /* VerificationResult */ ) ] ) ``` * `credentialErrors` : Any requested docTypes not returned by the holder. * `credentials` : A list of presented credentials with their data, status, and any attribute-specific errors. The [`sendProximityPresentationRequest`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-mobile-credential-verifier/send-proximity-presentation-request.html) method returns a [`MobileCredentialResponse`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier.dto/-mobile-credential-response/index.html) object. This response contains the presentation details provided by the holder, including any errors encountered and the verification status of the credentials returned in the response: ```kotlin title="MobileCredentialResponse structure" data class MobileCredentialResponse( val credentials: List, val credentialErrors: List ) ``` ```kotlin title="MobileCredentialPresentation structure" data class MobileCredentialPresentation( val docType: DocType, val validityInfo: MobileCredentialValidity, val claimErrors: Map>?, val claims: Map>?, val branding: Branding?, val issuerInfo: IssuerInfo?, val verificationResult: MobileCredentialVerificationResult ) ``` ```kotlin title="CredentialError structure" data class CredentialError( val docType: DocType, val errorCode: ErrorCode ) typealias DocType = String typealias ErrorCode = Int ``` * `credentialErrors` : Any requested docTypes not returned by the holder. * `credentials` : A list of presented credentials with their data, status, and any attribute-specific errors. The [`sendProximityPresentationRequest`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/functions/sendProximityPresentationRequest.html) returns a [`MobileCredentialResponse`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/types/MobileCredentialResponse.html) object. This response contains the presentation details provided by the holder, including any errors encountered and the verification status of the credentials returned in the response: ```tsx title="MobileCredentialResponse" const mobileCredentialResponse = { credentialErrors: [/* CredentialError */], credentials: [ { branding: /* Branding */, claimErrors: { /* Namespace */: { /* ElementID */: /* MobileCredentialResponseErrorCode */ } }, claims: { /* Namespace */: { /* ElementID */: /* MobileCredentialElementValue */ } }, docType: /* DocType */, issuerInfo: /* IssuerInfo */, validityInfo: /* Validity */, verificationResult: /* VerificationResult */ } ] }; ``` * `credentialErrors` : Any requested docTypes not returned by the holder. * `credentials` : A list of presented credentials with their data, status, and any attribute-specific errors. ## Next steps [#next-steps] * For your evaluation: * Note how long it took to configure and run the sample verifier app and complete an in-person verification. * Consider how the sample’s presentation request and UI map to your real in-person verification use cases (for example, which attributes you would request and how you would display them). * Explore the [in-person verification tutorial](/docs/verification/in-person-tutorial) for detailed instructions, configuration options, and production-grade patterns. * Review the [mDocs Verifier SDK overview](/docs/verification/sdks/overview) to understand platform support, capabilities, and how proximity and remote verification relate. # Learn how to build an application that can verify an mDoc presented via a proximity workflow URL: /docs/verification/in-person-tutorial ## Overview [#overview] In this tutorial you will use the [mDocs mobile verifier SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview) to build an application that can verify an [mDoc](/docs/concepts/mdocs) presented via a [proximity workflow](/docs/verification/in-person-overview) as per [ISO 18013-5](https://www.iso.org/standard/69084.html): Tutorial Workflow 1. The credential holder presents a QR code generated by their wallet application. 2. The verifier uses their application to scan the QR code, connect with the wallet and request an mDoc for verification. 3. The wallet application displays matching credentials to the holder and asks for consent to share them with the verifier. 4. The verifier application receives the wallet's response and verifies the provided credential. 5. Verification results are displayed to the verifier. The result will look something like this: To achieve this, you will build the following capabilities into your verifier application: * Initialize the SDK, so that your application can use its functions and classes. * Register a trusted issuer certificate, which enables your application to verify mDocs issued by that issuer. * Scan a QR code presented by a wallet application and establish a secure communication channel. * Send presentation requests to the wallet application, receive a presentation response and verify its content. * Display the results to the verifier app user. Tutorial Steps ## Prerequisites [#prerequisites] Before we get started, let's make sure you have everything you need. ### Prior knowledge [#prior-knowledge] * The proximity verification workflow described in this tutorial is based on the [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) standard. If you are unfamiliar with this standard, refer to the following Docs for more information: * What are [mDocs](/docs/concepts/mdocs)? * What is [credential verification](/docs/verification)? * Breakdown of the [proximity presentation workflow](/docs/verification/in-person-overview). * We assume you have experience developing applications in the relevant programming languages and frameworks (Swift for iOS, Kotlin for Android, and JavaScript/TypeScript for React Native). If you need to get a verifier solution up and running quickly with minimal development resources and in-house domain expertise, [talk to us](http://mattr.global/contact-us) about our white-label [MATTR GO Verify](https://mattr.global/platforms/go) which might be a good fit for you. ### Assets [#assets] * Use the [Get Started form](/docs/resources/get-started) to request a trial of MATTR **verification** capabilities. You will receive access to the following resources: * MATTR Pi mDocs Verifier SDK for your chosen platform (iOS, Android, or React Native). * MATTR VII tenant. * As part of your onboarding process you will be provided with access to the following assets: * ZIP file which includes the required framework: (`MobileCredentialVerifierSDK.xcframework.zip`). * Sample Verifier app: You can use this app for reference as we work through this tutorial. This tutorial is only meant to be used with the most [recent version](/docs/verification/sdks/overview#versions) of the iOS mDocs Verifier SDK. * Use the [Get Started form](/docs/resources/get-started) to request a trial of MATTR **verification** capabilities. You will receive access to the following resources: * MATTR Pi mDocs Verifier SDK for your chosen platform (iOS, Android, or React Native). * MATTR VII tenant. * As part of your onboarding process you will be provided with access to the following assets: * A ZIP file that includes the required library (`mobile-credential-verifier-*version*.zip`). * Sample Verifier app: You can use this app for reference as we work through this tutorial. This tutorial is only meant to be used with the most [recent version](/docs/verification/sdks/overview#versions) of the Android mDocs Verifier SDK. * Use the [Get Started form](/docs/resources/get-started) to request a trial of MATTR **verification** capabilities. You will receive access to the following resources: * MATTR Pi mDocs Verifier SDK for your chosen platform (iOS, Android, or React Native). * MATTR VII tenant. * As part of your onboarding process you will be provided with access to the following assets: * Access to the [@mattrglobal/mobile-credential-verifier-react-native](https://www.npmjs.com/package/@mattrglobal/mobile-credential-verifier-react-native) npm package. * Sample Verifier app: You can use this app for reference as we work through this tutorial. This tutorial is only meant to be used with the most [recent version](/docs/verification/sdks/overview#versions) of the React Native mDocs Verifier SDK. ### Development environment [#development-environment] * [Xcode](https://developer.apple.com/xcode/) setup with either: * Local build settings if you are developing locally. * [iOS developer account](https://developer.apple.com/programs/enroll/) if you intend to publish your app. * [Android Studio](https://developer.android.com/studio/). * Code editor (such as [VS Code](https://code.visualstudio.com/download)). * [Android Studio](https://developer.android.com/studio/). * [Xcode](https://developer.apple.com/xcode/). * [yarn](https://yarnpkg.com/) (v1.22.22 was used during development). * Java v17. This tutorial uses [Expo Go](https://expo.dev/go), leveraging [Development Builds](https://docs.expo.dev/develop/development-builds/introduction/). ### Testing devices [#testing-devices] As this tutorial implements a proximity presentation workflow, you will need two different mobile devices to test the end-to-end result: * Verifier device: * Supported iOS device to run the built Verifier application on, setup with: * Bluetooth access. * Available internet connection. * Holder device: * Mobile device with the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) installed and setup with: * Biometric authentication. * Bluetooth access. * Available internet connection. * Verifier device: * Supported Android device to run the built Verifier application on, setup with: * Bluetooth access. * Available internet connection. * [USB debugging](https://developer.android.com/studio/debug/dev-options#enable) enabled. * Holder device: * Mobile device with the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) installed and setup with: * Biometric authentication. * Bluetooth access. * Available internet connection. * Verifier device: * Supported iOS or Android device to run the built Verifier application on, setup with: * Bluetooth access. * Available internet connection. * Holder device: * Mobile device with the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) installed and setup with: * Biometric authentication. * Bluetooth access. * Available internet connection. ### Testing credential [#testing-credential] You will need a test credential to verify during this tutorial. You can use the MATTR GO Hold example app to claim a test mDoc by following these steps: 1. Download and install the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) on your **holder testing device**. 2. Launch the **MATTR GO Hold example app**. 3. Tap the Blue **Share** button. 4. Select **Respond or Collect**. This will open the camera view (You may need to allow the app to access your camera). 5. Scan the following QR code: QR Code 6. Follow the on-screen instructions to claim the credential (Note that this workflow requires an active internet connection). Got everything? Let's get going! ## Environment setup [#environment-setup] Tutorial Step 1 Perform the following steps to setup and configure your development environment: **Step 1: Create a new project** Please follow the detailed instructions to [Create a new Xcode Project](https://help.apple.com/xcode/mac/current/#/dev07db0e578) and add your organization's identifier. Create a new project **Step 2: Unzip the dependencies file** 1. Unzip the [`MobileCredentialVerifierSDK.xcframework.zip` file](#assets). 2. Drag the `MobileCredentialVerifierSDK.xcframework` folder into your project. 3. Configure `MobileCredentialVerifierSDK.xcframework` to [Embed & sign](https://help.apple.com/xcode/mac/current/#/dev51a648b07). See [Add existing files and folders](https://help.apple.com/xcode/mac/current/#/dev81ce1d383) for detailed instructions. This should result in the the following framework being added to your project: Framework added **Step 3: Add Bluetooth permissions** The SDK requires access to the mobile device Bluetooth capabilities as part of the proximity presentation workflow. [Configure these permissions](https://help.apple.com/xcode/mac/current/#/dev37c2f42ff) in the `Info` tab of the Application target: Privacy capabilities **Step 4: Run the application** Select **Run** and make sure the application launches with a *“Hello, world!”* text in the middle of the display, as shown in the following image: Application ready **Step 1: Create a new project** 1. [Create a new Android Studio project](https://developer.android.com/studio/projects/create-project), using the *Empty Activity* template. Create a new Android project 2. Name the project `Verifier Tutorial`. 3. Select *API 24* as the `Minimum SDK` version. 4. Select *Kotlin DSL* as the `Build configuration language`. Project configuration 5. Select **Finish**. 6. [Sync](https://developer.android.com/build#sync-files) the project with Gradle files. **Step 2: Add required dependencies** 1. Select the [Project view](https://developer.android.com/studio/projects#ProjectView). Project view 2. Create a new directory named `repo` in your project's folder. 3. Unzip the `mobile-credential-verifier-*version*.zip` file and copy the unzipped `global` folder into the new `repo` folder. Unzipped files copied 4. Open the `settings.gradle.kts` file in the `VerifierTutorial` folder and add the following Maven repository to the `dependencyResolutionManagement.repositories` block: ```kotlin title="settings.gradle.kts" maven { url = uri("repo") } ``` 5. Open the `app/build.gradle.kts` file in your app folder and add the following dependencies to the `dependencies` block: ```kotlin title="app/build.gradle.kts" implementation("global.mattr.mobilecredential:verifier:7.0.0") implementation("androidx.navigation:navigation-compose:2.9.0") ``` * The `verifier` dependency version should match the version of the unzipped `mobile-credential-verifier-*version*.zip` file you copied to the `repo` folder. * The required `navigation-compose` version may differ based on your version of the IDE, Gradle, and other project dependencies. 6. Open the `gradle/libs.versions.toml` file and pin the following libraries versions: ```kotlin title="gradle/libs.versions.toml" coreKtx = "1.18.0" lifecycleRuntimeKtx = "2.10.0" ``` 7. [Sync](https://developer.android.com/build#sync-files) the project with Gradle files. 8. Open the [Build](https://developer.android.com/studio/run#gradle-console) tab and select `Sync` to make sure that the project has synced successfully. Synced successfully **Step 3: Run the application** 1. Connect a [debuggable](https://developer.android.com/studio/debug/dev-options#Enable-debugging) mobile device to your machine. 2. [Build and run the app](https://developer.android.com/studio/run) on the connected mobile device. The app should launch with a *“Hello, Android!”* text displayed: Blank app **Step 1: Access the tutorial codebase** 1. Access the tutorial starter codebase by either: * Cloning the MATTR sample-apps repository: ```bash title="Clone the repository" git clone https://github.com/mattrglobal/sample-apps.git ``` or * Downloading just the starter directory using the [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Freact-native-in-person-verifier-tutorial%2Freact-native-in-person-verifier-tutorial-starter) utility. 2. Open the tutorial project in your code editor. You can find it in the `sample-apps/react-native-in-person-verifier-tutorial/react-native-in-person-verifier-tutorial-starter/` directory. You can find the completed tutorial code in the `sample-apps/react-native-in-person-verifier-tutorial/react-native-in-person-verifier-tutorial-complete` directory and use it as a reference as you work along this tutorial. **Step 2: iOS Application configuration** 1. Open the `app.config.ts` file and update the `bundleIdentifier` value under the `// Update the bundle identifier` comment to a unique value for your application, e.g. `com.mycompany.myapp`. ```typescript title="app.config.ts" bundleIdentifier: "com.mycompany.myapp", ``` iOS requires each app to have a unique bundle identifier for App Store and development environments. 2. Add the following camera and Bluetooth permissions to the `ios.infoPlist` object under the `// Add necessary permissions for camera and Bluetooth` comment: ```ts title="app.config.ts" NSCameraUsageDescription: "Camera is used to scan QR codes.", NSBluetoothAlwaysUsageDescription: "This app uses Bluetooth to communicate with verifiers or holders.", NSBluetoothPeripheralUsageDescription: "This app uses Bluetooth to communicate with verifiers or holders.", ``` These permissions are required for the app to use the camera for QR code scanning, and Bluetooth for proximity communication with the holder's app. **Step 3: Configure the app plugins** Add the following code under the `// Configure the app plugins` comment to import required plugin configurations: ```ts title="app.config.ts" "./withMobileCredentialAndroidVerifierSdk", [ "expo-build-properties", { android: { minSdkVersion: 24, compileSdkVersion: 36, targetSdkVersion: 34, kotlinVersion: "2.0.21", }, }, ], [ "expo-camera", { cameraPermission: "Allow $(PRODUCT_NAME) to access your camera", }, ], ``` The SDK requires platform-specific configurations to work correctly. A plugin file specifically for Android has already been created in your project root directory. You can also follow the instructions in the [mDocs Verifier](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:platform-android) SDK Docs to perform this platform-specific configuration manually. **Step 4: Install the dependencies** 1. Open a terminal in the project's root and navigate to the starter project directory: ```bash title="Navigate to the project directory" cd sample-apps/react-native-in-person-verifier-tutorial/react-native-in-person-verifier-tutorial-starter/ ``` 2. Install the application dependencies: ```bash title="Install dependencies" yarn install ``` **Step 5: Generate the iOS and Android project files** Run the following command to generate the iOS and Android project files: ```bash title="Generate project files" yarn expo prebuild ``` You should now see the `ios` and `android` folders in your project root. **Step 6: Start the application** Connect your testing device(s) and run the following command to start the application(s): **iOS** ```bash title="Run iOS application" yarn ios --device ``` **Android** ```bash title="Run Android application" yarn android --device ``` Nice work, your application is now all set to begin using the SDK! ## Configure SDK Tethering [#configure-sdk-tethering] The iOS and Android Verifier SDKs must be **tethered** to a MATTR VII tenant. On initialization, the SDK registers your app instance with the tenant and obtains a license, so SDK Tethering must be configured before you initialize the SDK. For a full explanation of tethering and the capabilities it enables, see [SDK Tethering](/docs/verification/sdks/sdk-tethering). To enable SDK Tethering, create a Verifier Application on your MATTR VII tenant: Make a request of the following structure to create an iOS Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `ios`. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID (must match the Team ID used to sign your app). Get it from your Apple Developer account or from Xcode under **Signing & Capabilities**. * `appAttest`: App Attest configuration for the iOS verifier application: * `required`: When `true`, the app instance must provide a valid App Attest attestation during registration and token renewal. When `false`, the app can fall back to assertion-only authentication. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `environment`: The App Attest environment (`development` or `production`). Apple recommends using `development` for testing and `production` for distribution builds. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", // [!code focus] "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. Make a request of the following structure to create an Android Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false }, "openid4vpConfiguration": { "redirectUri": "com.yourcompany.verifierapp://oid4vp-callback" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `android`. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/verification/android-app-signing) for more information. * `keyAttestation`: Key Attestation configuration for the Android verifier application: * `required`: When `true`, the app instance must provide a valid Key Attestation during registration and token renewal. When `false`, the app can register and renew tokens using just an authentication assertion. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `openid4vpConfiguration.redirectUri`: Required by the create-application endpoint, which needs at least one of `openid4vpConfiguration` or `dcApiConfiguration`. In-person proximity verification does not use this redirect, so any valid custom-scheme URI is accepted here. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", // [!code focus] "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. SDK Tethering is currently not required for the React Native Verifier SDK. ## Initialize the SDK [#initialize-the-sdk] Tutorial Step 2 The first capability you will build into your app is to initialize the SDK so that your app can use SDK functions and classes. To achieve this, we need to import the `MobileCredentialVerifierSDK` framework and then initialize the `MobileCredentialVerifier` class. ### Step 1: Create the application structure [#step-1-create-the-application-structure] 1. Open the `ContentView` file in your new project and replace any existing code with the following: ```swift title="ContentView" import SwiftUI // Initialize SDK - Step 2.1: Import MobileCredentialVerifierSDK struct ContentView: View { @State var viewModel: VerifierViewModel = VerifierViewModel() var body: some View { NavigationStack(path: $viewModel.navigationPath) { VStack { Button("Scan QR Code") { viewModel.navigationPath.append(NavigationState.scanQRCode) } .padding() Button("View Response") { viewModel.navigationPath.append(NavigationState.viewResponse) } .padding() } .navigationDestination(for: NavigationState.self) { destination in switch destination { case .scanQRCode: codeScannerView case .viewResponse: presentationResponseView } } } .task { await viewModel.setupCertificates() } } // MARK: Verification Views var codeScannerView: some View { // Verify mDocs - Step 2.4: Create QRScannerView EmptyView() } var presentationResponseView: some View { // Verify mDocs - Step 4.2: Create PresentationResponseView EmptyView() } } // MARK: VerifierViewModel @Observable final class VerifierViewModel { var navigationPath = NavigationPath() // Initialize SDK - Step 2.2: Add MobileCredentialVerifier var // Verify mDocs - Step 1.1: Create MobileCredentialRequest instance // Verify mDocs - Step 1.2: Create receivedDocuments variable // Initialize SDK - Step 2.3: Initialize the SDK func setupCertificates() async { // Setup certificates - Step 2: Add trusted issuer certificates print("This method will add the trust anchor to the sdk storage") } } // MARK: Proximity Presentation extension VerifierViewModel { func setupProximityPresentationSession(_ deviceEngagementString: String) { // Verify mDocs - Step 3.2: Create setupProximityPresentationSession print("This method will use qr code string do setup proximity session") } func sendDeviceRequest() { // Verify mDocs - Step 3.3: Create sendDeviceRequest function print("This method will send preconfigured device request to holder app") } } // Verify mDocs - Step 3.1: Extend VerifierViewModel class // MARK: - Navigation enum NavigationState: Hashable { case scanQRCode case viewResponse } ``` This will serve as the basic structure for your application. We will copy and paste different code snippets into specific locations to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step. We recommend copying and pasting the comment text in Xcode search field (e.g. `// Initialize SDK - Step 2.2: Add MobileCredentialVerifier var`) to easily locate it in the code. Open the `app/src/main/java/com.example.verifiertutorial/MainActivity.kt` file in your project and replace any existing code with the following: ```kotlin title="MainActivity.kt" package com.example.verifiertutorial import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.example.verifiertutorial.ui.theme.VerifierTutorialTheme import global.mattr.mobilecredential.verifier.dto.MobileCredentialResponse import global.mattr.mobilecredential.verifier.MobileCredentialVerifier import global.mattr.mobilecredential.verifier.platformconfig.PlatformConfiguration import global.mattr.mobilecredential.verifier.exception.VerifierException.FailedToRegisterException import global.mattr.mobilecredential.verifier.exception.VerifierException.InvalidLicenseException import kotlinx.coroutines.launch import java.net.URL class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Initialize SDK - Step 1.1: Initialize the SDK enableEdgeToEdge() setContent { VerifierTutorialTheme { val navController = rememberNavController() NavHost( modifier = Modifier .fillMaxSize() .padding(vertical = 72.dp, horizontal = 8.dp), startDestination = "home", navController = navController, ) { composable("home") { HomeScreen(navController) } composable("scanQr") { // Verify mDocs - Step 1.7: Add "Scan QR Code" screen call } composable("viewResponse") { // Verify mDocs - Step 4.4: Add "View Response" screen call } } } } } } @Composable fun HomeScreen(navController: NavController) { Column(modifier = Modifier.fillMaxWidth()) { Button( modifier = Modifier.fillMaxWidth(), onClick = { navController.navigate("scanQr") } ) { Text("Scan QR Code") } } } // Verify mDocs - Step 2.2: Add shared data ``` This will serve as the basic structure for your application. We will copy and paste different code snippets into specific locations in this codebase to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step. 1. Open the `App.tsx` file in your project and replace the existing code with the skeleton structure: ```tsx title="App.tsx" import { type MobileCredentialResponse, addTrustedIssuerCertificates, createProximityPresentationSession, getTrustedIssuerCertificates, initialize, sendProximityPresentationRequest, terminateProximityPresentationSession, } from "@mattrglobal/mobile-credential-verifier-react-native"; // import { QRScannerModal } from "./QRScannerModal"; // import { VerificationResultsModal } from "./VerificationResultsModal"; import { useCameraPermissions } from "expo-camera"; import { StatusBar } from "expo-status-bar"; import { useEffect, useState } from "react"; import { ActivityIndicator, Alert, SafeAreaView, Text, TouchableOpacity, View } from "react-native"; import { styles } from "./styles"; export default function App() { // State variables for SDK initialization, UI and loading messages const [isSDKInitialized, setIsSDKInitialized] = useState(false); const [loadingMessage, setLoadingMessage] = useState(false); const [verificationResults, setVerificationResults] = useState(null); // Modal states const [isScanning, setIsScanning] = useState(false); const [showVerificationResults, setShowVerificationResults] = useState(false); const [permission, requestPermission] = useCameraPermissions(); // Initialize SDK - Step 2.1: Initialize the SDK // Verify mDocs - Step 1.2: Create handleQRCodeDetected function return ( mDocs Verifier {loadingMessage ? ( {loadingMessage} ) : ( {/* Verify mDocs - Step 1.5: Create Scan QR Code Button */} {!isSDKInitialized && SDK not initialized. Please restart the app.} )} {/* Verify mDocs - Step 1.4: Use QRScannerModal */} {/* Verify mDocs - Step 2.3: Use VerificationResultModal */} ); } ``` This will serve as the basic structure for your application. We will add code to specific locations to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step. We recommend using your editor's search functionality to locate comments like `// Initialize SDK - Step 1.3: Initialize the SDK` when adding new code. ### Step 2: Initialize the MobileCredentialVerifier class [#step-2-initialize-the-mobilecredentialverifier-class] 1. Add the following code after the `// Initialize SDK - Step 2.1: Import MobileCredentialVerifierSDK` comment to import `MobileCredentialVerifierSDK` and gain access to the SDK's capabilities: ```swift title="ContentView" import MobileCredentialVerifierSDK ``` 2. Add the following code after the `// Initialize SDK - Step 2.2: Add MobileCredentialVerifier var` comment to create a variable that holds the `mobileCredentialVerifier` instance: ```swift title="ContentView" var mobileCredentialVerifier: MobileCredentialVerifier // Holds the asynchronous initialization work so other calls can await it // before using the SDK (see Step 2.3). private var initializationTask: Task? ``` 3. Add the following code after the `// Initialize SDK - Step 2.3: Initialize the SDK` comment to assign a shared instance of the class to our `mobileCredentialVerifier` variable and initialize the SDK: ```swift title="ContentView" init() { mobileCredentialVerifier = MobileCredentialVerifier.shared // Keep a handle to the initialization Task so later SDK calls can await it. initializationTask = Task { do { let platformConfiguration = PlatformConfiguration( tenantHost: Constants.tenantHost, applicationId: Constants.applicationId ) try await mobileCredentialVerifier.initialize(platformConfiguration: platformConfiguration) } catch let error as MobileCredentialVerifierError { // Print the underlying reason so registration failures are visible. // failedToRegister carries the cause (for example an App Attest // App ID / team mismatch); invalidLicense means no valid license. print("SDK initialization failed:", error) throw error } } } ``` SDK Tethering requires a `platformConfiguration`, so `initialize` now takes one and is asynchronous (called here from a `Task`). `platformConfiguration` contains the following properties, which we will add as constants in the next step: * `tenantHost`: The URL of your MATTR VII tenant where your Verifier Application is configured. * `applicationId`: The `id` returned when you [created the Verifier Application](#configure-sdk-tethering). Network access is required the first time the SDK initializes (for registration) and when the license is later renewed. 4. Create a new file named `Constants.swift` and add the following, replacing the placeholders with your own values: ```swift title="Constants.swift" import Foundation enum Constants { static let tenantHost = URL(string: "https://your-tenant.vii.mattr.global")! static let applicationId = "" } ``` * `tenantHost`: The URL of your MATTR VII tenant, available in the MATTR Portal under **Platform Management > Tenant**. * `applicationId`: The `id` returned when you [created the Verifier Application](#configure-sdk-tethering). 5. [Run](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device) the app to ensure it compiles successfully. Once the app launches you will see a screen with three buttons, each leading to an empty view. In the following steps, you will implement proximity presentation functionalities into these views. 1. Add the following code after the `// Initialize SDK - Step 1.1: Initialize the SDK` comment to initialize the SDK (ensure you replace the `tenantHost` and `applicationId` placeholders with your own values): We recommend leaving the comment text (e.g. `// Initialize SDK - Step 1.1: Initialize the SDK`) even after you have pasted the code snippet, as it will later help you to easily locate the step in the code. ```kotlin title="MainActivity.kt" lifecycleScope.launch { val platformConfiguration = PlatformConfiguration( tenantHost = URL("https://your-tenant.vii.mattr.global"), applicationId = "" ) try { MobileCredentialVerifier.initialize(this@MainActivity, platformConfiguration) // Setup certificates - Step 2.2: Add trusted issuer certificates } catch (e: FailedToRegisterException) { // Registration with the MATTR VII tenant failed — check connectivity and configuration } catch (e: InvalidLicenseException) { // The SDK license is missing, invalid, or expired } } ``` Initializing the SDK requires a `PlatformConfiguration` object with the following properties: * `tenantHost`: The URL of your MATTR VII tenant where your Verifier Application is configured. You can find this in the MATTR Portal under **Platform Management > Tenant**. * `applicationId`: The `id` returned when you [created the Verifier Application](#configure-sdk-tethering). Network access is required the first time the SDK initializes (for registration) and when the license is later renewed. 2. [Run](https://developer.android.com/studio/run#basic-build-run) the app to make sure it compiles properly. 1. Add the following code after the `// Initialize SDK - Step 2.1: Initialize the SDK` comment to initialize the SDK: We recommend leaving the comment text (e.g. `// Initialize SDK - Step 2.1: Initialize the SDK`) even after you have pasted the code snippet, as it will later help you to easily locate the step in the code. ```tsx title="App.tsx" useEffect(() => { const initializeSDK = async () => { try { setLoadingMessage("Initializing SDK..."); const result = await initialize(); if (result.isErr()) { console.error("Failed to initialize SDK:", result.error); Alert.alert("Error", "Failed to initialize the verifier SDK"); return; } setIsSDKInitialized(true); // Setup certificates - Step 3: Register the trusted IACA certificate on first launch } catch (error) { console.error("Failed to initialize SDK:", error); Alert.alert("Error", "Failed to initialize the verifier SDK"); } finally { setLoadingMessage(false); } }; initializeSDK(); }, []); ``` 2. Run the app. ## Setup certificates [#setup-certificates] Tutorial Step 3 Once the SDK is initialized, the next step is to add a trusted issuer certificate. Tutorial Workflow Every mDoc is signed using a certificate chain, also known as a [chain of trust](/docs/concepts/chain-of-trust). To verify a presented mDoc, your application must confirm that this chain leads back to a trusted root certificate, called an [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca). To do this, your application must provide the SDK with the IACA certificates for every issuer it should trust. In this tutorial, you will add the IACA certificate for the MATTR Labs test issuer, which was used to issue the credential you will verify. 1. Create a new file called `IACAs.swift` and add the following code: ```swift title="IACAs.swift" import Foundation enum IACAs { static let mattrLabs = """ -----BEGIN CERTIFICATE----- MIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG EwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew HhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp MCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp dB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq 232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu bWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp ZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp bGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny bDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI SNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6 -----END CERTIFICATE----- """.trimmingCharacters(in: .whitespacesAndNewlines) } ``` This file contains the root certificate of the MATTR Labs test issuer. 2. Return to the `ContentView.swift` file and replace the `print` statement under the comment `// Setup certificates - Step 2: Add trusted issuer certificates` with the following: ```swift title="ContentView.swift" do { // Wait for initialization to finish before using the SDK. This returns // immediately once initialize has completed, and rethrows if it failed. try await initializationTask?.value _ = try await mobileCredentialVerifier.addTrustedIssuerCertificates(certificates: [IACAs.mattrLabs]) } catch { print("Failed to add trusted issuer certificate:", error) } ``` This function will be called as soon as the app view appears and the certificate will be added to the app. 1. Create a new file called `Iacas.kt` and add the following code: ```kotlin title="Iacas.kt" package com.example.verifiertutorial object Iacas { val mattrLabs = """ -----BEGIN CERTIFICATE----- MIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG EwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew HhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp MCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp dB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq 232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu bWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp ZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp bGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny bDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI SNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6 -----END CERTIFICATE----- """.trimIndent() } ``` This file contains the root certificate of the MATTR Labs test issuer. 2. Return to the `MainActivity.kt` file and add the following code after the `// Setup certificates - Step 2.2: Add trusted issuer certificates` comment to add the MATTR Labs test issuer certificate to the SDK: ```kotlin title="MainActivity.kt" MobileCredentialVerifier.addTrustedIssuerCertificates(listOf(Iacas.mattrLabs)) ``` 1. In your project root, create a new file called `certificates.ts` and add the following code: ```ts title="certificates.ts" // MATTR Labs test issuer (montcliff-dmv.mattrlabs.com) IACA export const MONTCLIFF_DMV_IACA = `MIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG EwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew HhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp MCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp dB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq 232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu bWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp ZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp bGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny bDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI SNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6`; ``` This file contains the root certificate of the MATTR Labs test issuer. 2. Back in `App.tsx`, import the certificate alongside your existing imports: ```tsx title="App.tsx" import { MONTCLIFF_DMV_IACA } from "./certificates"; ``` 3. Add the following code after the `// Setup certificates - Step 3: Register the trusted IACA certificate on first launch` comment to register the MATTR Labs test issuer certificate with the SDK the first time the app runs: ```tsx title="App.tsx" setLoadingMessage("Loading certificates..."); const certificates = await getTrustedIssuerCertificates(); if (certificates.length === 0) { await addTrustedIssuerCertificates([MONTCLIFF_DMV_IACA]); } ``` The SDK persists trusted certificates between launches, so this check ensures the sample certificate is only added once. ## Verify mDocs [#verify-mdocs] Tutorial Step 4 In this part we will build the components that enable a verifier app to verify an mDoc presented via a proximity workflow as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html): Tutorial Workflow To achieve this, your application must be able to: 1. Create a presentation request that defines the information required for verification. 2. Scan and process a QR code presented by a wallet application. Your application must retrieve the information from that QR code and use it to establish a secure connection between the verifier and holder devices. 3. Your verifier application then uses this secure connection to send a presentation request to which the holder wallet application responds with a presentation response. 4. Finally, the SDK verifies any mDocs included in the response, stores the verification results in a variable and makes them available to your application to display. Your application will use the SDK's `createProximityPresentationSession` function that takes a string retrieved from the QR code and uses it to establish a proximity presentation session with the wallet application and initiate the presentation workflow. This function takes a `listener` argument of type `ProximityPresentationSessionListener` delegate, which will receive proximity presentation session events. **Step 1: Create a presentation request** As a verifier, you can select what information you request for verification. Your application implements this by creating a [MobileCredentialRequest](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialrequest) instance to define the required information, and a new variable to hold the response from the wallet application. 1. Open the `ContentView` file and add the following code under the `// Verify mDocs - Step 1.1: Create MobileCredentialRequest instance` comment to define what information to request from the wallet application user: ```swift title="ContentView" let mobileCredentialRequest = MobileCredentialRequest( docType: "org.iso.18013.5.1.mDL", namespaces: [ "org.iso.18013.5.1": [ "family_name": false, "given_name": false, "birth_date": false ] ] ) ``` This object details: * The requested credential type (e.g. `org.iso.18013.5.1.mDL`). * The claims required for verification (e.g. `family_name`). * The requested namespace (e.g. `org.iso.18013.5.1`). * Whether or not the verifier intends to persist the claim value (`true`/`false`). For the verification to be successful, the presented credential must include the referenced claim against the specific namespace defined in the request. Our example requests the `birth_date` under the `org.iso.18013.5.1` namespace. If a wallet responds to this request with a credential that includes a `birth_date` but rather under the `org.iso.18013.5.1.US` namespace, the claim will not be verified. To simplify the tutorial, this is a hardcoded request. However, once you are comfortable with the basic functionalities you can create a UI in your verifier application that enables the user to create different requests on the fly by selecting different claims to include. Check out our [GO Verify app](/docs/verification/go-verify/getting-started) to see this in action. 2. Add the following code under the `Verify mDocs - Step 1.2: Create receivedDocuments variable` comment to create a new `receivedDocuments` variable that will hold the response from the wallet application: ```swift title="ContentView" var receivedDocuments: [MobileCredentialPresentation] = [] ``` Your application now has an existing credential request to share, and a variable to hold any incoming responses. In the next step we will build the capabilities to send this request and handle the response. **Step 2: Scan and process a QR code** Tutorial Workflow As defined in ISO/IEC 18130-5:2021, a [proximity presentation workflow](/docs/verification/in-person-overview) is always initiated by the holder (wallet application user), who must create a QR code for the verifier to scan in order to initiate the [device engagement phase](/docs/verification/in-person-overview#engagement-phase). Tutorial Workflow This means that your verifier application must be able to scan and process this QR code. For ease of implementation, we will use a third party framework to achieve this. 1. Add [camera usage permissions](https://help.apple.com/xcode/mac/current/#/dev37c2f42ff) to the app target: Camera permissions 2. Add the [CodeScanner](https://github.com/twostraws/CodeScanner) library via [Swift Package Manager](https://help.apple.com/xcode/mac/current/#/devb83d64851). Code scanner package 3. [Create a new swift file](https://help.apple.com/xcode/mac/current/#/dev81ce1d383) named `QRScannerView` and add the following code into it to implement the QR scanning capability: ```swift title="QRScannerView" import SwiftUI import CodeScanner import AVFoundation struct QRScannerView: View { private let completionHandler: (String) -> Void init(completion: @escaping (String) -> Void) { completionHandler = completion } var body: some View { CodeScannerView(codeTypes: [.qr]) { result in switch result { case .failure(let error): print(error.localizedDescription) case .success(let result): print(result.string) completionHandler(result.string) } } } } ``` 4. Back in the `ContentView` file, replace the `EmptyView()` under the `// Verify mDocs - Step 2.4: Create QRScannerView` comment with the following code to create a new app view that the user will use to scan a QR code: ```swift title="ContentView" QRScannerView( completion: { string in viewModel.setupProximityPresentationSession(string) } ) ``` 5. [Run](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device) the app and select the **Scan QR Code** button. You should be navigated to the new `QRScannerView` where you can use the camera to scan a QR code. Next we will build the logic that handles this QR code to establish a secure connection with the wallet application. **Step 3: Exchange presentation request and response** 1. Add the following code under the `Verify mDocs - Step 3.1: Extend VerifierViewModel class` to extend the `VerifierViewModel` class with the [`ProximityPresentationSessionListener`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/proximitypresentationsessionlistener) protocol: ```swift title="ContentView" extension VerifierViewModel: ProximityPresentationSessionListener { public func onEstablished() { sendDeviceRequest() } // Session-creation failures (Bluetooth permission, transport setup, // unsupported curve, and so on) are delivered here, not to onTerminated. // onError has an empty default implementation, so without this method // those failures would be silent. public func onError(error: (any Error)?) { print("Proximity session error:", error?.localizedDescription ?? "unknown") } public func onTerminated(error: (any Error)?) { print("Session terminated:", error?.localizedDescription ?? "none") } } ``` Now, as soon as a connection is established, the app will send a device request. You will implement the functionality of `sendDeviceRequest()` in `VerifierViewModel` later in the tutorial. If a session cannot be created, `onError` reports the reason. 2. Replace the `print` statement under the `// Verify mDocs - Step 3.2: Create setupProximityPresentationSession` comment with the following code to call the SDK's [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialverifier/createproximitypresentationsession\(encodeddeviceengagementstring:listener:\)) function, passing a device engagement string (retrieved from a QR code) and `self` as a listener to create a proximity presentation session: ```swift title="ContentView" mobileCredentialVerifier.createProximityPresentationSession(encodedDeviceEngagementString: deviceEngagementString, listener: self) ``` 3. Replace the `print` statement under the `// Verify mDocs - Step 3.3: Create sendDeviceRequest function` comment with following code to implement the logic to send a device request: ```swift title="ContentView" Task { @MainActor in receivedDocuments = [] do { // Navigate to response screen navigationPath.append(NavigationState.viewResponse) // Request mDocs let deviceResponse = try await mobileCredentialVerifier.sendProximityPresentationRequest( request: [mobileCredentialRequest] ) // Assign new values from the response receivedDocuments = deviceResponse.credentials // Terminate session after response is received (optional) await mobileCredentialVerifier.terminateProximityPresentationSession() } catch { print(error) receivedDocuments = [] } } ``` This function now implements the following logic: 1. Navigate to the `viewResponse` screen. 2. Send a proximity presentation request using the SDK's [`requestMobileCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/proximitypresentationsession/requestmobilecredentials\(request:\)) function. 3. Store the wallet response in the `deviceResponse` variable. This includes the verification results of any credentials included in the response. 4. Store the verification results in the `receivedDocuments` variable. 5. Terminate the presentation session once the response is received. **Step 4: Display verification results** 1. Create a new file named `DocumentView` and add the following code to display available verification results: ```swift title="DocumentView" import MobileCredentialVerifierSDK import SwiftUI struct DocumentView: View { var viewModel: DocumentViewModel var body: some View { VStack(alignment: .leading, spacing: 10) { Text(viewModel.docType) .font(.title) .fontWeight(.bold) .padding(.bottom, 5) Text(viewModel.verificationResult) .font(.title) .fontWeight(.bold) .foregroundStyle(viewModel.verificationFailedReason == nil ? .green : .red) .padding(.bottom, 5) if let verificationFailedReason = viewModel.verificationFailedReason { Text(verificationFailedReason) .font(.title3) .fontWeight(.bold) .foregroundStyle(.red) .padding(.bottom, 5) } ForEach(viewModel.namespacesAndClaims.keys.sorted(), id: \.self) { key in VStack(alignment: .leading, spacing: 5) { Text(key) .font(.headline) .padding(.vertical, 5) .padding(.horizontal, 10) .background(Color.gray.opacity(0.2)) .cornerRadius(5) ForEach(viewModel.namespacesAndClaims[key]!.keys.sorted(), id: \.self) { claim in HStack { Text(claim) .fontWeight(.semibold) Spacer() Text(viewModel.namespacesAndClaims[key]![claim]! ?? "") .fontWeight(.regular) } .padding(.vertical, 5) .padding(.horizontal, 10) .background(Color.white) .cornerRadius(5) .shadow(radius: 1) } } .padding(.vertical, 5) } if !viewModel.claimErrors.isEmpty { Text("Failed Claims:") .font(.headline) .padding(.vertical, 5) ForEach(viewModel.claimErrors.keys.sorted(), id: \.self) { key in VStack(alignment: .leading, spacing: 5) { Text(key) .font(.headline) .padding(.vertical, 5) .padding(.horizontal, 10) .background(Color.gray.opacity(0.2)) .cornerRadius(5) ForEach(viewModel.claimErrors[key]!.keys.sorted(), id: \.self) { claim in HStack { Text(claim) .fontWeight(.semibold) Spacer() Text(viewModel.claimErrors[key]![claim]! ?? "") .fontWeight(.regular) } .padding(.vertical, 5) .padding(.horizontal, 10) .background(Color.white) .cornerRadius(5) .shadow(radius: 1) } } .padding(.vertical, 5) } } } .padding() .background(RoundedRectangle(cornerRadius: 10).fill(Color.white).shadow(radius: 5)) .padding(.horizontal) } } // MARK: DocumentViewModel @Observable class DocumentViewModel { let docType: String let namespacesAndClaims: [String: [String: String?]] let claimErrors: [String: [String: String?]] let verificationResult: String let verificationFailedReason: String? init(from presentation: MobileCredentialPresentation) { self.docType = presentation.docType self.verificationResult = presentation.verificationResult.verified ? "Verified" : "Invalid" self.verificationFailedReason = presentation.verificationResult.failureType?.rawValue self.namespacesAndClaims = presentation.claims?.reduce(into: [String: [String: String]]()) { result, outerElement in let (outerKey, innerDict) = outerElement result[outerKey] = innerDict.mapValues { $0.textRepresentation } } ?? [:] self.claimErrors = presentation.claimErrors?.reduce(into: [String: [String: String]]()) { result, outerElement in let (outerKey, innerDict) = outerElement result[outerKey] = innerDict.mapValues { "\($0)" } } ?? [:] } } // MARK: Helper extension MobileCredentialElementValue { var textRepresentation: String { switch self { case .bool(let bool): return "\(bool)" case .string(let string): return string case .int(let int): return "\(int)" case .unsigned(let uInt): return "\(uInt)" case .float(let float): return "\(float)" case .double(let double): return "\(double)" case let .date(date): let dateFormatter = DateFormatter() dateFormatter.dateStyle = .short dateFormatter.timeStyle = .none return dateFormatter.string(from: date) case let .dateTime(date): let dateFormatter = DateFormatter() dateFormatter.dateStyle = .short dateFormatter.timeStyle = .short return dateFormatter.string(from: date) case .data(let data): return "Data \(data.count) bytes" case .map(let dictionary): let result = dictionary.mapValues { value in value.textRepresentation } return "\(result)" case .array(let array): return array.reduce("") { partialResult, element in partialResult + element.textRepresentation } .appending("") @unknown default: return "Unknown type" } } } ``` The `DocumentView` file comprises the following elements: * `DocumentView` : Basic UI layout for viewing received documents and verification results. * `DocumentViewModel` : This class takes `MobileCredentialPresentation` and converts its elements into strings to display in the `DocumentView`. * Extension of `MobileCredentialElementValue` which converts the values of received claims into a human-readable format. 2. Return to the `ContentView` file and replace the `EmptyView()` under the `// Verify mDocs - Step 4.2: Create PresentationResponseView` comment with the following code to display the `DocumentView` view when verification results are available: ```swift title="ContentView" ZStack { if viewModel.receivedDocuments.isEmpty { VStack(spacing: 40) { Text("Waiting for response...") .font(.title) ProgressView() .progressViewStyle(.circular) .scaleEffect(2) } } else { ScrollView { ForEach(viewModel.receivedDocuments, id: \.docType) { doc in DocumentView(viewModel: DocumentViewModel(from: doc)) .padding(10) } } } } ``` **Step 1: Create a screen for scanning the credential offer** Tutorial Workflow As defined in ISO/IEC 18130-5:2021, a [proximity presentation workflow](/docs/verification/in-person-overview) is always initiated by the holder (wallet application user), who must create a QR code for the verifier to scan in order to initiate the [device engagement phase](/docs/verification/in-person-overview#engagement-phase). Tutorial Workflow This means that your verifier application must be able to scan and process this QR code. For ease of implementation, we will use a third party framework to achieve this. 1. Add dependencies to your `app/build.gradle.kts`: ```kotlin title="app/build.gradle.kts" implementation("com.google.accompanist:accompanist-permissions:0.36.0") implementation("com.journeyapps:zxing-android-embedded:4.3.0") ``` 2. [Sync project with Gradle files](https://developer.android.com/build#sync-files). 3. In your package, create a new file called `ScanQrScreen.kt` and add the following code: ```kotlin title="ScanQrScreen.kt" import android.app.Activity import android.Manifest import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView import androidx.navigation.NavController import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState import com.journeyapps.barcodescanner.BarcodeCallback import com.journeyapps.barcodescanner.DecoratedBarcodeView import global.mattr.mobilecredential.verifier.deviceretrieval.devicerequest.DataElements import global.mattr.mobilecredential.verifier.deviceretrieval.devicerequest.NameSpaces import global.mattr.mobilecredential.verifier.dto.MobileCredentialRequest import global.mattr.mobilecredential.verifier.dto.MobileCredentialResponse import global.mattr.mobilecredential.verifier.MobileCredentialVerifier import global.mattr.mobilecredential.verifier.ProximityPresentationSessionListener import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException @OptIn(ExperimentalPermissionsApi::class) @Composable fun ScanQrScreen(activity: Activity, navController: NavController) { // Verify mDocs - Step 1.6: Add permission request logic } // Verify mDocs - Step 1.5: Add screen content // Verify mDocs - Step 1.4: Add QR scan callback // Verify mDocs - Step 3.1: Create session listener // Verify mDocs - Step 2.1: Create a sample request ``` 4. In the `ScanQrScreen.kt` file, add the following code under the `// Verify mDocs - Step 1.4: Add QR scan callback` comment to define a callback that is called when the QR code was successfully scanned (we will implement the callback logic at a later stage): ```kotlin title="ScanQrScreen.kt" private fun onQrScanned( activity: Activity, deviceEngagement: String, coroutineScope: CoroutineScope, navController: NavController ) { coroutineScope.launch { // Verify mDocs - Step 3.2: Create session // Verify mDocs - Step 4.1: Handle response } } ``` 5. Add the following code under the `// Verify mDocs - Step 1.5: Add screen content` comment to define the main UI of the screen: ```kotlin title="ScanQrScreen.kt" @Composable private fun Content(activity: Activity, navController: NavController) { val context = LocalContext.current val barcodeView = remember { DecoratedBarcodeView(context) } val coroutineScope = rememberCoroutineScope() var isQrScanned by remember { mutableStateOf(false) } val barcodeCallback = remember { BarcodeCallback { result -> onQrScanned(activity, result.text, coroutineScope, navController) barcodeView.pause() isQrScanned = true } } DisposableEffect(Unit) { barcodeView.decodeContinuous(barcodeCallback) barcodeView.resume() onDispose { barcodeView.pause() } } if (!isQrScanned) { AndroidView(factory = { barcodeView }, modifier = Modifier.fillMaxSize()) } else { Box(Modifier.fillMaxSize()) { CircularProgressIndicator(Modifier.align(Alignment.Center)) } } } ``` Please have a quick look at the code. The screen will show a QR scanning view. As soon as the QR code is captured, it shows a progress spinning wheel, and calls `onQrScanned` function. 6. Add the following code under the `// Verify mDocs - Step 1.6: Add permission request logic` comment to define a basic logic for requesting the camera access permission at runtime: ```kotlin title="ScanQrScreen.kt" val cameraPermissionState = rememberPermissionState(Manifest.permission.CAMERA) val requestPermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) {} LaunchedEffect(cameraPermissionState) { if (!cameraPermissionState.status.isGranted) { requestPermissionLauncher.launch(Manifest.permission.CAMERA) } } if (cameraPermissionState.status.isGranted) Content(activity, navController) ``` 7. Back in the `MainActivity` file, add the following code under the `// Verify mDocs - Step 1.7: Add "Scan QR" screen call` to connect the created screen to the navigation graph: ```kotlin title="MainActivity.kt" ScanQrScreen(this@MainActivity, navController) ``` 8. [Run](https://developer.android.com/studio/run#basic-build-run) the app and select the **Scan QR Code** button. You should be navigated to the new `QRScannerView` where you can use the camera to scan a QR code. Now we will build the logic that handles this QR code to establish a secure connection with the wallet application. **Step 2: Create a presentation request** As a verifier, you can select what information you request for verification. Our application implements this by creating a [MobileCredentialRequest](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier.dto/-mobile-credential-request/index.html) instance to define the required information, and a new variable to hold the response from the wallet application. 1. In the `ScanQrScreen.kt` file, add the following code under the `// Verify mDocs - Step 2.1: Create a sample request` comment to define what information to request from the holder's application: ```kotlin title="ScanQrScreen.kt" private val sampleMdocRequest = MobileCredentialRequest( docType = "org.iso.18013.5.1.mDL", namespaces = NameSpaces( mapOf( "org.iso.18013.5.1" to DataElements( listOf("given_name", "family_name", "birth_date").associateWith { false } ) ) ) ) ``` This object details: * The requested credential type (e.g. `org.iso.18013.5.1.mDL`). * The claims required for verification (e.g. `given_name`). * The requested namespace (e.g. `org.iso.18013.5.1`). * Whether or not the verifier intends to persist the claim value (`true`/`false`). For the verification to be successful, the presented credential must include the referenced claim against the specific namespace defined in the request. Our example requests the `birth_date` under the `org.iso.18013.5.1` namespace. If a wallet responds to this request with a credential that includes a `birth_date` but rather under the `org.iso.18013.5.1.US` namespace, the claim will not be verified. To simplify the tutorial, this is a hardcoded request. However, once you are comfortable with the basic functionalities you can create a UI in your verifier application that enables the user to create different requests on the fly by selecting different claims to include. See our [GO Verify app](/docs/verification/go-verify/getting-started) as an example. 2. Back in the `MainActivity.kt` file, add the following code under the `// Verify mDocs - Step 2.2: Add shared data` comment to create a new `credentialResponse` variable that will hold the response from the holder's application: ```kotlin title="MainActivity.kt" object SharedData { var credentialResponse: MobileCredentialResponse? = null } ``` Now your application has an existing request to share, and a variable to hold any incoming responses. We can now proceed to build the capabilities to send the request and handle the response. **Step 3: Exchange presentation request and response** 1. In `ScanQrScreen.kt`, add the following code under the `// Verify mDocs - Step 3.1: Create session listener` to define a listener that will react to the proximity presentation session lifecycle events: ```kotlin title="ScanQrScreen.kt" private class SessionListener( private val coroutineScope: CoroutineScope, private val continuation: Continuation ) : ProximityPresentationSessionListener { override fun onEstablished() { coroutineScope.launch { // Verify mDocs - Step 3.3: Request credentials } } override fun onTerminated(error: Throwable?) { /* no-op */ } override fun onError(error: Throwable?) { error?.let { continuation.resumeWithException(it) } } } ``` 2. Add the following code under the `// Verify mDocs - Step 3.2: Create session` to create proximity presentation session and register a session listener: ```kotlin title="ScanQrScreen.kt" SharedData.credentialResponse = try { suspendCancellableCoroutine { continuation: Continuation -> val sessionListener = SessionListener(coroutineScope, continuation) MobileCredentialVerifier .createProximityPresentationSession(activity, deviceEngagement, sessionListener) } } catch (e: Exception) { Toast.makeText(activity, "Failed to request credentials", Toast.LENGTH_SHORT).show() null } ``` We pass `Continuation` to the listener. It will be resumed either with `MobileCredentialResponse` if the whole presentation flow is successful, or with an exception if there was an issue during any stage of the credentials presentation. 3. Add the following code under the `// Verify mDocs - Step 3.3: Request credentials` to request the mobile credentials: ```kotlin title="ScanQrScreen.kt" try { val response = MobileCredentialVerifier.sendProximityPresentationRequest( listOf(sampleMdocRequest) ) MobileCredentialVerifier.terminateProximityPresentationSession() continuation.resume(response) } catch (e: Exception) { continuation.resumeWithException(e) } ``` The resulting code: 1. Establishes a secure connection with the wallet application by calling [`createProximityPresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-mobile-credential-verifier/create-proximity-presentation-session.html?query=fun%20createProximityPresentationSession\(activity:%20Activity,%20qrEncodedDeviceEngagementString:%20String,%20listener:%20ProximityPresentationSessionListener\)). 2. Calls [`sendProximityPresentationRequest`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-mobile-credential-verifier/send-proximity-presentation-request.html) function as soon as the session is established. The function accepts a list of [`MobileCredentialRequest`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier.dto/-mobile-credential-request/index.html?query=data%20class%20MobileCredentialRequest\(val%20docType:%20DocType,%20val%20namespaces:%20NameSpaces\)), sends the requests to the wallet application, receives a response from the wallet application, and verifies any mDocs included in the response. 3. Stores the response in the `SharedData.credentialResponse` value. 4. Handles the exceptions, if they were thrown from the above calls. An exception can be thrown if, for example, the Bluetooth connection between the Holder and Verifier devices was interrupted during the session. Now that we have the verification results stored, you can implement different business logics to handle the results. For this tutorial, we will display these results to the verifier app user, individually indicating the verification status of each claim included in the request. **Step 4: Display verification results** 1. Add the following code under the `// Verify mDocs - Step 4.1: Handle response` to navigate the user to the response screen, where they can see the retrieved credentials, if the retrieval was successful: ```kotlin title="ScanQrScreen.kt" SharedData.credentialResponse?.let { navController.navigate("viewResponse") { popUpTo("home") } } ``` 2. Create a new file named `ViewResponseScreen.kt` that will be used to display the response to the verifier application user. 3. Copy and paste the following code into the new file: ```kotlin title="ViewResponseScreen.kt" import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Card import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import global.mattr.mobilecredential.verifier.deviceretrieval.deviceresponse.DataElementIdentifier import global.mattr.mobilecredential.verifier.deviceretrieval.deviceresponse.NameSpace import global.mattr.mobilecredential.verifier.dto.MobileCredentialElement @Composable fun ViewResponseScreen() { // Verify mDocs - Step 4.5: Define content } // Verify mDocs - Step 4.8: Display claims // Verify mDocs - Step 4.7: Map a claim or an error to string ``` 4. Back in the `MainActivity.kt` file, add the following code under the `// Verify mDocs - Step 4.4: Add "View Response" screen call` comment to connect the created composable to the navigation graph: ```kotlin title="MainActivity.kt" ViewResponseScreen() ``` 5. Return to the `ViewResponseScreen.kt` screen and add the following code under the `// Verify mDocs - Step 4.5: Define content` comment to define the basic UI for displaying the response details to the verifier application user: ```kotlin title="ViewResponseScreen.kt" val credential = SharedData.credentialResponse?.credentials?.firstOrNull() if (credential == null || SharedData.credentialResponse?.credentialErrors?.isNotEmpty() == true) { // Verify mDocs - Step 4.6: Show error } else { Column( modifier = Modifier .fillMaxSize() .verticalScroll(rememberScrollState()), verticalArrangement = Arrangement.spacedBy(4.dp), ) { // Verify mDocs - Step 4.10: Show credential verification status // Verify mDocs - Step 4.9: Show retrieved claims and errors } } ``` While our SDK [allows](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-mobile-credential-verifier/send-proximity-presentation-request.html) to request multiple document types (and thus, multiple credentials) at the same time, for the tutorial simplicity we requested only one document type, and expect to see only one mobile credential as the response. Because of that, we take and handle only the first element from the retrieved credentials list. 6. Add the following code under the `// Verify mDocs - Step 4.6: Show error` comment to show an error message in case the response is empty or if there were major errors during the response retrieval: ```kotlin title="ViewResponseScreen.kt" Box(Modifier.fillMaxSize()) { Text("There were errors while receiving the response", Modifier.align(Alignment.Center)) } ``` 7. Add the following code under the `// Verify mDocs - Step 4.7: Map a claim or an error to string` comment to map the received claim value (or a claim error) to a string: ```kotlin title="ViewResponseScreen.kt" private fun Any.claimToUiString() = when (this) { is MobileCredentialElement -> { when (this) { is MobileCredentialElement.ArrayElement, is MobileCredentialElement.DataElement, is MobileCredentialElement.MapElement -> this::class.simpleName ?: "Unknown element" else -> value.toString() } } else -> "Not returned" } ``` *Claim error* here means that the presentation session has completed successfully, without interruption, and the mobile credentials were received and verified, but some of the claim values were not sent to the verifier. Refer to the [Handling verification results](/docs/verification/in-person-guides/handling-verification-results) guide for more information. 8. Add the following code under the `// Verify mDocs - Step 4.8: Display claims` comment to create a function that displays the retrieved and failed claims to the verifier application user: ```kotlin title="ViewResponseScreen.kt" @Composable private fun ColumnScope.Claims( title: String, claims: Map>? ) { Text( title, modifier = Modifier .fillMaxWidth() .align(Alignment.CenterHorizontally), style = MaterialTheme.typography.titleLarge ) claims?.forEach { (namespace, claims) -> Card { Column(Modifier.padding(6.dp)) { Text( namespace, style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(vertical = 4.dp) ) claims.forEach { (name, value) -> Row { Text(name, Modifier .weight(1f) .padding(end = 4.dp)) Text(value.claimToUiString(), overflow = TextOverflow.Ellipsis) } } } } } ?: Text("Nothing here") } ``` 9. Add the following code under the `// Verify mDocs - Step 4.9: Show retrieved claims and errors` comment to create the UI for showing the retrieved and failed claims on the screen: ```kotlin title="ViewResponseScreen.kt" Claims("Received claims", credential.claims) Spacer(Modifier.padding(8.dp)) Claims("Failed claims", credential.claimErrors) ``` 10. Add the following code under the `// Verify mDocs - Step 4.10: Show credential verification status` comment to show the overall verification status: ```kotlin title="ViewResponseScreen.kt" val statusStyle = MaterialTheme.typography.titleLarge if (credential.verificationResult.verified) { Text("Verified", style = statusStyle, color = Color.Green) } else { Text("Not verified", style = statusStyle, color = Color.Red) } ``` **Step 1: Create a component for scanning a QR code presented by the holder** 1. Create a new file called `QRScannerModal.tsx` and add the following code into it to implement the QR scanning capability: ```tsx title="QRScannerModal.tsx" import { CameraView } from "expo-camera"; import { useEffect, useRef, useState } from "react"; import { Alert, Modal, SafeAreaView, Text, TouchableOpacity, View } from "react-native"; import { styles } from "./styles"; interface QRScannerModalProps { visible: boolean; onClose: () => void; permission: any; requestPermission: () => Promise; onQRCodeDetected: (data: string) => void; } export function QRScannerModal({ visible, onClose, permission, requestPermission, onQRCodeDetected, }: QRScannerModalProps) { const [scanned, setScanned] = useState(false); const [scanningEnabled, setScanningEnabled] = useState(true); const handlerCalledRef = useRef(false); const handleBarCodeScanned = ({ data }: { data: string }) => { if (!scanningEnabled || scanned || handlerCalledRef.current) { return; } // Immediately mark as handled and disable scanning handlerCalledRef.current = true; setScanningEnabled(false); setScanned(true); console.log(`Scanned barcode with data: ${data}`); // Check if data starts with "mdoc:" if (!data || !data.startsWith("mdoc:")) { Alert.alert( "Invalid QR Code", "The QR code must be an mDoc QR code starting with 'mdoc:'. Please scan a valid mDoc QR code.", [ { text: "Try Again", onPress: () => resetScanner(), }, ] ); return; } console.log("Valid mDoc QR code detected:", data); // Close modal immediately to stop camera onClose(); // Call handler after modal is closed to prevent camera from firing again setTimeout(() => { onQRCodeDetected(data); }, 300); }; const resetScanner = () => { handlerCalledRef.current = false; setScanned(false); setScanningEnabled(true); }; const handleClose = () => { resetScanner(); onClose(); }; // Reset scanner state when modal becomes visible useEffect(() => { if (visible) { resetScanner(); } }, [visible]); if (!visible) return null; return ( QR Code Scanner Close {!permission ? ( Camera permissions are still loading ) : !permission.granted ? ( Camera permission is required to scan QR codes Request Permission ) : ( <> {!scanned && ( )} {scanned ? "Processing QR code..." : "Point your camera at a QR code"} {scanned && ( Scan Again )} )} ); } ``` * This component uses the expo-camera package and handles camera permissions through props passed from `App.tsx`. * It's configured to scan QR codes and validates that the scanned data starts with "mdoc:" prefix. * The `handleBarCodeScanned` function processes the scanned data and calls the `onQRCodeDetected` callback with the QR code data. 2. Return to your `App.tsx` file and add the following code under the `// Verify mDocs - Step 1.2: Create handleQRCodeDetected function` comment to add a presentation workflow handler. This handler uses the SDK's `createProximityPresentationSession` and `sendProximityPresentationRequest` and methods to establish a session and request a credential: ```tsx title="App.tsx" const handleQRCodeDetected = async (qrData: string) => { try { setLoadingMessage("Establishing secure connection..."); await createProximityPresentationSession({ deviceEngagement: qrData, onEstablished: async () => { console.log("Session established successfully"); setLoadingMessage("Requesting verification data..."); try { const response = await sendProximityPresentationRequest({ mobileCredentialRequests: [ { docType: "org.iso.18013.5.1.mDL", namespaces: { "org.iso.18013.5.1": { family_name: false, given_name: false, birth_date: false, }, }, }, ], }); if (response.isErr()) { throw new Error(`Failed to verify presentation: ${response.error.message}`); } setLoadingMessage("Verifying credentials..."); setVerificationResults(response.value); setShowVerificationResults(true); await terminateProximityPresentationSession(); } catch (error) { console.error("Error during presentation request:", error); Alert.alert("Error", "Failed to verify mDocs"); await terminateProximityPresentationSession(); } finally { setLoadingMessage(false); } }, onTerminated: () => { console.log("Session terminated"); setLoadingMessage(false); }, onError: (error) => { console.error("Session error:", JSON.stringify(error, null, 2)); Alert.alert( "Error", `Session failed: ${error.message || JSON.stringify(error)}`, ); setLoadingMessage(false); }, }); console.log( "createProximityPresentationSession call completed (waiting for callbacks)", ); } catch (error) { console.error("Error during QR code processing:", error); Alert.alert( "Error", `Failed to process QR code: ${error instanceof Error ? error.message : String(error)}`, ); setLoadingMessage(false); } }; ``` This function requests an mDL (mobile driver's license) credential with specific data elements: family\_name, given\_name, and birth\_date. 3. Uncomment the QRScannerModal import at the top of the file to integrate the QR Scanner modal: ```tsx title="App.tsx" import { QRScannerModal } from "./QRScannerModal"; ``` 4. Paste the following code under the `// Verify mDocs - Step 1.4: Use QRScannerModal` comment to render the QR Scanner modal component in your app: ```tsx title="App.tsx" setIsScanning(false)} permission={permission} requestPermission={requestPermission} onQRCodeDetected={handleQRCodeDetected} /> ``` 5. Add the following code under the `Verify mDocs - Step 1.5: Create Scan QR Code Button` comment to create a button that opens the QR Scanner modal: ```tsx title="App.tsx" setIsScanning(true)} > Scan QR Code ``` **Step 2: Display verification results** 1. Create the Verification Results modal in a new file called `VerificationResultsModal.tsx` and pasted the following code into it. `MobileCredentialResponse` is the type that holds the verification results from the MATTR Verifier SDK. The modal will display the verification results, including any claims and errors. ```tsx title="VerificationResultsModal.tsx" import type { MobileCredentialResponse } from "@mattrglobal/mobile-credential-verifier-react-native"; import { Modal, SafeAreaView, ScrollView, Text, TouchableOpacity, View } from "react-native"; import { styles } from "./styles"; interface VerificationResultsModalProps { visible: boolean; onClose: () => void; verificationResults: MobileCredentialResponse | null; } export function VerificationResultsModal({ visible, onClose, verificationResults }: VerificationResultsModalProps) { if (!visible || !verificationResults) return null; // Helper function to render different claim value types function renderClaimValue(claim: any): string { if (!claim) return "undefined"; if (claim.type === "array" || claim.type === "object") { return JSON.stringify(claim.value); } return String(claim.value); } return ( Verification Results Close {verificationResults.credentials && verificationResults.credentials.length > 0 ? ( {/* Basic verification status */} {verificationResults.credentials[0].verificationResult?.verified ? "✓ Verified" : "✗ Verification Failed"} {verificationResults.credentials[0].docType} {/* Display the raw credential data */} {verificationResults.credentials.map((credential, credIndex) => ( {/* Claims data */} {credential.claims && Object.keys(credential.claims).map((namespace, nsIndex) => ( {namespace} {credential.claims && Object.entries(credential.claims[namespace]).map(([key, value], idx) => ( {key}: {renderClaimValue(value)} ))} ))} {/* Error information */} {!credential.verificationResult?.verified && credential.verificationResult?.reason && ( Verification Failed: Type: {credential.verificationResult.reason.type} Message: {credential.verificationResult.reason.message} )} {/* Claim errors if any */} {credential.claimErrors && Object.keys(credential.claimErrors).length > 0 && ( Claim Errors {Object.entries(credential.claimErrors).map(([namespace, errors]) => Object.entries(errors).map(([elementId, errorCode]) => ( {namespace}.{elementId}: Error: {errorCode} )) )} )} ))} ) : ( No data available )} ); } ``` 2. Return to your `App.tsx` file and uncomment the VerificationResultsModal import to integrate the verification results modal: ```tsx title="App.tsx" import { VerificationResultsModal } from "./VerificationResultsModal"; ``` 3. Add the following code under the `// Verify mDocs - Step 2.3: Use VerificationResultModal` comment to display the verification results modal when verification is complete: ```tsx title="App.tsx" setShowVerificationResults(false)} verificationResults={verificationResults} /> ``` ## Test the end-to-end workflow [#test-the-end-to-end-workflow] Tutorial Step 5 1. Run the verifier app. The MATTR Labs test issuer certificate is registered with the SDK automatically on first launch. 2. Open your **holder testing device** and launch the **GO Hold example app**. 3. Select the **Wallet** button. 4. Locate the mDoc claimed as part of the [prerequisites](#mdoc) for this tutorial and select the **share button** to display a QR code. 5. Use your **verifier testing device** and select the **Scan QR Code** button. 6. Use the **verifier testing device** to scan the QR code displayed on the **holder testing device**. 7. Use the **holder testing device** to consent to sharing the information with the verifier. 8. Use the **verifier testing device** and select the **View response** button. You should see a result similar to the following: 1. The wallet app user creates a QR code to initiate the proximity presentation workflow. 2. The verifier app scans the QR code, establishes a secure connection and sends a presentation request. 3. The wallet app user reviews the presentation request and agrees to share matching mDocs with the verifier. 4. The verifier app receives and verifies the mDocs included in the presentation response. 5. The verifier app user views the verification results. Congratulations! Your verifier application can now verify mDocs presented via a proximity presentation workflow, as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). ## Summary [#summary] You have just used the [mDocs Verifier SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview) to build an application that can verify an [mDoc](/docs/concepts/mdocs) presented via a [proximity workflow](/docs/verification/in-person-overview) as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html): Tutorial Workflow This was achieved by building the following capabilities into the application: * Initialize the SDK, so that your application can use its functions and classes. * Register a trusted issuer certificate, which enables your application to verify mDocs issued by that issuer. * Scan a QR code presented by a wallet application and establish a secure communication channel. * Send presentation requests to the wallet application, receive a presentation response and verify its content. * Display the results to the verifier app user. ## What's next? [#whats-next] * You can check out SDKs reference documentation to learn more about available functions and classes: * [iOS](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk) * [Android](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) * [React Native](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest) * You can implement [NFC based device engagement](/docs/verification/in-person-quickstart#listen-to-nfc-session-requests) capabilities (currently supported by the Android Verifier SDK and the React Native SDK for Android platforms only). # Credential Verification URL: /docs/verification [Mobile documents (mDocs)](/docs/concepts/mdocs) are the emerging standard for high-assurance digital credentials. [Mobile driver's licenses (mDLs)](/docs/concepts/mdl) are the most prominent and most adopted example to date, but the same verification capabilities apply to any high-assurance credential built on the mDoc family of standards, including national identity cards, residence permits, professional licenses, and other [credentials beyond the driver's license](/docs/concepts/mdocs-beyond-mdl). As an implementer, you need a verification solution that handles the complexity of standards compliance, trust establishment, and cross-platform support, without requiring deep cryptographic expertise. This overview walks you through the key decisions and steps for adding mDoc verification to your application, whether you're verifying in person at a counter, remotely through a website, or within a native mobile app. Start here, then follow the pages in order or jump to the topic you need. ## How mDoc verification works [#how-mdoc-verification-works] An mDoc is a digital credential stored in a holder's wallet, issued by a trusted authority. Unlike traditional document scans or OCR (optical character recognition)-based checks, mDoc verification is cryptographic: your application validates a digitally signed credential against a trusted issuer's certificate chain. A complete verification covers both identity and information assurance: * **Identity assurance**: resolve the issuer's identifier and confirm the credential was signed by a trusted issuer, typically by checking the issuer's certificate against a local trust list or an [external trust registry](/docs/digital-trust-service). * **Information assurance**: verify the digital signature to confirm integrity, validate the credential format against its referenced specification, and check that the credential is currently active (not expired or [revoked](/docs/issuance/revocation/overview)). Verifying a credential does not include evaluating the truth of the claims encoded in the credential. Verification confirms only that these are the same claims signed by the issuer and that the credential has not been tampered with. In practice, this means: * **No visual inspection required**: Verification is automated and tamper-evident. * **Selective disclosure**: Holders share only the data points you request (e.g., "over 18" without revealing full date of birth). See [selective disclosure](/docs/concepts/selective-disclosure) for the underlying mechanism. * **Offline capable**: In-person verification can work without network connectivity. * **Standards-based**: Built on [ISO/IEC 18013-5, 18013-7 and 23220](/docs/concepts/iso-mdoc-standards), ensuring interoperability across wallets and issuers. ## Underlying platforms [#underlying-platforms] MATTR Credential Verification capabilities You can use different MATTR VII, MATTR Pi and/or MATTR GO capabilities to verify different [credential formats](/docs/concepts/formats-overview) based on your use case: * **MATTR VII**: * [In-person](/docs/verification/in-person-overview) verification of [CWT and Semantic CWT credentials](/docs/concepts/cwt). * [Remote](/docs/verification/remote-overview) verification of [mDocs](/docs/verification/remote-overview#mdocs). * **MATTR Pi**: * [In-person](/docs/verification/in-person-overview) verification of [CWT and Semantic CWT credentials](/docs/concepts/cwt). * [In-person](/docs/verification/in-person-overview) and [Remote](/docs/verification/remote-overview) verification of [mDocs](/docs/concepts/mdocs). * **MATTR GO**: * [In-person](/docs/verification/in-person-overview) verification of [CWT and Semantic CWT credentials](/docs/concepts/cwt) and [mDocs](/docs/concepts/mdocs). ## Explore the overview [#explore-the-overview] Work through these pages to design your verification solution from simple to more complex concepts: 1. [Choose your verification channel](/docs/verification/choosing-a-channel): in-person, remote web, or remote mobile. 2. [Decide what data you need to verify](/docs/verification/deciding-what-to-verify): selective disclosure and presentation requests. 3. [Embed the SDKs into your application](/docs/verification/embedding-the-sdks): integration approach, SDK options, and backend configuration. 4. [Establish trust with issuers](/docs/verification/establishing-trust): trusted issuer certificates and managed trust lists. 5. [Handling verification results](/docs/verification/handling-results): interpreting and acting on results. 6. [Frequently asked questions](/docs/verification/faq): answers to common verification questions. # OpenID for Verifiable Presentations (OID4VP) URL: /docs/verification/oid4vp MATTR's remote mDocs verification capabilities implement the [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) specification. While OID4VP supports online presentation and verification of other credential formats, MATTR's implementation is specific to online presentation and verification of mDocs, as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) Annex B and the draft version of ISO/IEC 18013-7 Annex D. ## Basic OID4VP workflow [#basic-oid4vp-workflow] The interaction comprises the following steps, based on the [OAuth 2.0 mechanism](https://www.rfc-editor.org/info/rfc6749): ### Authorization request [#authorization-request]
Authorization request
When a user interacts with a verifier application which requires presenting an mDoc, the verifier application responds with a request URI. The verifier can configure the request URI to invoke any registered wallet application or define a specific registered wallet application that will be used to handle this authorization request. The wallet uses the request URI to retrieve the request object, which includes a presentation definition detailing the information the verifier requires. It can include the type and format(s) of credential(s) and any individual claims required from those credential(s).
### Gathering matching credentials [#gathering-matching-credentials]
Gathering matching credentials
The wallet determines what available mDocs match the presentation request, authenticates the holder and requests for consent to present the required information. Upon consent, the wallet creates a verifiable presentation(s) from matching verifiable credential(s) the holder has consented to share.
### Authorization response [#authorization-response]
Authorization response
The wallet sends the encrypted verifiable presentation response to the verifier’s application response endpoint as an authorization response. This response includes a verifiable presentation contained inside a `vp_token` parameter. The verifier provides a secure redirect URL in the response to redirect the user back to where they started the interaction.
### Verifying the verifiable presentation [#verifying-the-verifiable-presentation]
Verifying the verifiable presentation
Once the verifier receives the verifiable presentation, they can apply their own business logic and validation rules to verify the content of the presentation.
OID4VP supports both [same-device](#same-device-verification-workflow) and [cross-device](#cross-device-verification-workflow) verification workflows: * **Same-device workflows** involve a single device which houses both the verifier and wallet applications. * **Cross-device workflows** involve two different devices: * The first device houses the verifier application and issues a presentation request. * The second device houses a wallet application that responds with the requested information. ## Same-device verification workflow [#same-device-verification-workflow]
Same-device verification workflow
Same-device workflows include a single device and two different applications: * One web/mobile application is used to verify mDocs. It is used to create a presentation request and verify the incoming presentation response. In our example this is the *BankApp*. * One mobile application is used to hold and present mDocs. It is used to receive a presentation request, authenticate the holder, gather matching credentials and share them (upon holder's consent) with the verifier. In our example this is the *WalletApp*. This workflow uses simple redirects to exchange the authorization request and response between the two applications. For example, consider a scenario where a user performs a financial transaction in a banking web application using their mobile device. Upon initiating the transaction, the banking application requests a proof of the user's identity. The digital wallet receives this request and seeks the holder's consent to send a presentation response with a matching credential, such as a drivers' license, national ID card or passport. Upon receiving consent, the presentation response is signed using a private key with its corresponding public key attested by the issuer and included in the credential. This private key is typically stored in the mobile platform secure key store, which must be unlocked by biometrics, verifying the identity of the holder. The wallet then sends the presentation response to the verifier, who verifies both the credential and the presentation's signature. If the public key found in the credential can be used to verify the presentation’s signature, the verifier can confirm the identity of the entity presenting the credential. With authentication confirmed, the transaction proceeds, all within the confines of the user's single mobile device. Same-device flows typically have few variations and predominantly make use of mobile platform features such as deep links and redirects to enable a verifying application to interface with a wallet application. The technical protocols underlying this flow allow both the verifier and wallet applications to be either platform-native or web-based applications. ## Cross-device verification workflow [#cross-device-verification-workflow] Cross-device verification workflow Cross-device verification workflows include two devices: * One device is used to create the presentation request and verify the incoming presentation response. * One device is used to receive the presentation request, authenticate the holder, gather matching mDocs and share them (upon holder's consent) with the verifier. While the same-device flow uses a deep-link to invoke the digital wallet, in a cross-device flow the link is rendered as a QR code, which is then scanned by the holder to invoke their digital wallet and enable them to present the required credentials. Additionally, at the end of a cross-device flow the holder is not redirected on their mobile device, but rather continues the interaction on the device where they started it. Following up on our previous example, consider a financial transaction conducted on a bank's online portal, where the user begins the interaction on their desktop browser. When they are asked to present an mDoc for verification, the user is presented with a QR code which they scan with their mobile device. Upon scanning the QR code, their digital wallet is invoked and displays what information is required by the bank for verification. The user consents to sharing the information, and upon successful verification returns to their desktop browser to complete the interaction. # Privacy in credential verification URL: /docs/verification/privacy Description: How MATTR's verification capabilities handle presented data, why verification does not require contacting the issuer, and what verifiers should do to minimize their data footprint. Verification is the moment when a presented credential is checked against a set of trust rules. This page describes how MATTR's verification capabilities handle the presented data, why verification does not require contacting the issuer, and the practical guidance for verifiers who want to minimize the data they hold. For the broader picture, see [Privacy in MATTR's architecture](/docs/concepts/privacy). ## Verification is a local cryptographic check [#verification-is-a-local-cryptographic-check] MATTR's verification capabilities check four things on each presented credential: * The credential's digital signature is valid. * The signing key chains back to a trusted issuer (either via a local trust list or via an external [Digital Trust Service](/docs/digital-trust-service)). * The credential structure conforms to the relevant standard (for example, [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) for mDocs). * Optional time-based checks (validity period, expiry, revocation status) where applicable. All four checks can be performed locally by the verifier. There is no protocol-level requirement to contact the issuer at presentation time. This is a deliberate architectural property: it lets verification work offline, removes a single point of failure, and prevents the issuer from observing verification activity. For details on the checks performed on each credential type, see [Credential verification](/docs/verification) and the format-specific overviews. ## What MATTR's verifier sees and what it does not retain [#what-mattrs-verifier-sees-and-what-it-does-not-retain] The verifier receives only the attributes the holder consented to release. With selective disclosure, this is typically a small subset of the credential. The verification capability: * Computes the cryptographic checks. * Returns the verification result to the calling application. * Does not persist the credential content. If the verifier's business application wants to retain attributes from the presentation (for example, the user's age band to demonstrate compliance with an age-restricted purchase law), that retention is an application-level decision. It is not done by MATTR's verification capability by default. The cryptographic proof itself can be discarded once the verification result has been recorded. The proof is bound to a specific presentation and is not designed to be re-presented later. Verifying a credential does not include evaluating the truth of the claims encoded in the credential. It confirms only that the claims are the ones the issuer signed and that the credential is structurally valid. The verifier still decides what business action to take based on the disclosed claims. ## Selective disclosure in verification [#selective-disclosure-in-verification] The flip side of [selective disclosure on the holder side](/docs/holding/privacy) is that the verifier requests only what it needs. When a verifier sends a presentation request, it specifies the attributes it wants. The wallet shows the user what is being requested, the user consents, and the wallet produces a proof that reveals only those attributes. The verifier receives the requested attributes and a cryptographic proof that they came from a credential signed by the issuer. This shifts the verifier's data discipline upstream. Instead of receiving the full credential and then storing only what is needed, the verifier requests only what is needed in the first place and never sees the rest. See [Selective disclosure](/docs/concepts/selective-disclosure) for a fuller treatment of the concept. ## Checking revocation without exposing the holder [#checking-revocation-without-exposing-the-holder] Revocation is the area where verification comes closest to a live issuer interaction, and care is needed to preserve privacy properties. MATTR supports revocation via status lists. A status list is a published list of indices and binary revocation values that the issuer maintains and the verifier fetches when it needs to check status. Two privacy properties matter here: * **Status lists do not contain personal information.** The list is a sequence of binary values indexed by position. There is no link from a status list entry to the holder's identity. * **Fetching a status list does not identify the holder.** The verifier requests the list as a whole (or a chunk of it). The issuer can see that some verifier fetched the list, but cannot tell which specific credential or holder triggered the check. Verifiers who care about minimizing issuer-side correlation can cache status lists locally and refresh them on a schedule independent of individual verifications. ## Trust establishment [#trust-establishment] The verifier needs a way to know which issuers it should trust. There are two patterns: * **Local trust lists.** The verifier maintains its own list of trusted issuer IACAs or DIDs and evaluates each presentation against that list. * **External trust via a Digital Trust Service.** The verifier consumes trust information from a DTS (for example, via a VICAL or an Ecosystem policy). The DTS is the network operator's tool for managing trust across many participants. See [Privacy in digital trust services](/docs/digital-trust-service/privacy) for how DTS consumption preserves the architecture's privacy properties. In neither case does the verifier need to contact the issuer at presentation time. ## Practical guidance for verifiers [#practical-guidance-for-verifiers] * Request only the attributes you need to make the decision you are making. If you are checking age eligibility, request the age-attestation attribute, not the date of birth. * Treat the cryptographic proof as transient. Record the verification result, not the proof itself, unless you have a specific evidentiary requirement. * Cache status lists locally and refresh them on a schedule. Do not fetch the list once per verification if you can avoid it. * If you need to retain disclosed attributes for compliance reasons, scope the retention period to the minimum required by law and document the basis. The architecture supports a minimal retention model; the deployment has to honor it. * Be transparent with the holder about why you are requesting each attribute. Trust frameworks increasingly require verifiers to declare a purpose as part of the presentation request, and many wallets surface this to the user. * Choose the regional MATTR deployment that matches your data residency obligations. Verifier configuration data stays within the chosen AWS region except for listed sub-processors. ## Frequently asked questions [#frequently-asked-questions] ### Does the verifier need to contact the issuer to verify a credential? [#does-the-verifier-need-to-contact-the-issuer-to-verify-a-credential] No. The credentials MATTR supports carry a cryptographic signature that can be verified locally against the issuer's published public key or the relevant trust anchor. The verifier does not need a live connection to the issuer at presentation time. ### Does MATTR's verifier store the presented credential? [#does-mattrs-verifier-store-the-presented-credential] Not by default. The verification capability performs the cryptographic checks required to confirm the credential's authenticity and validity, returns the result, and does not retain the credential content. If the verifier wants to retain specific attributes, that retention is a deliberate, application-level decision. ### Is the issuer told when a credential is verified? [#is-the-issuer-told-when-a-credential-is-verified] No. The verification check is local. The only case where a verifier may interact with an issuer-controlled endpoint is when checking a status list for revocation, and even that interaction does not identify the holder. ### How do I check whether a credential has been revoked without leaking the holder's identity? [#how-do-i-check-whether-a-credential-has-been-revoked-without-leaking-the-holders-identity] Revocation is checked against published status lists. A status list contains an index and a binary revocation value. The verifier fetches the status list (which may cover many credentials) and reads the relevant index. The issuer cannot tell which specific holder's status was checked. ### Can a verifier request more attributes than it needs? [#can-a-verifier-request-more-attributes-than-it-needs] Technically, yes. The architecture cannot prevent a verifier from asking for too much. The protections against over-collection are the user's ability to decline, the wallet's consent UI, the issuer's policy, and accreditation rules in the relevant trust framework. ## Related reading [#related-reading] * [Privacy in MATTR's architecture](/docs/concepts/privacy) * [Selective disclosure](/docs/concepts/selective-disclosure) * [Decentralized trust model](/docs/concepts/decentralized-trust-model) * [Remote verification overview](/docs/verification/remote-overview) * [In-person verification overview](/docs/verification/in-person-overview) * [Digital Trust Service](/docs/digital-trust-service) # React Native Verifier SDK v9.0.0 Migration Guide URL: /docs/verification/react-native-9.0.0-migration-guide Description: A guide to help developers migrate from React Native Verifier SDK v8.x to v9.0.0, including breaking changes, new features, and best practices. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in React Native Verifier SDK v9.0.0, including breaking changes, new features, and migration steps. ## Key Features [#key-features] * **App to app verification**: The React Native Verifier SDK can now be used to request credentials from another app on the same device using OID4VP. This allows you to build verification flows directly into your apps and have a holder app on the same device respond. * **Verify with Apple Wallet (iOS Only)**: The SDK can now request and verify a credential directly from an Apple Wallet using the Verify with Wallet API. Only available for iOS 16 and above. * **Improved reliability in contactless flows**: Enhanced BLE performance delivers more consistent proximity credential exchanges and faster engagements. * **Status Lists Draft 14 Support**: The SDK now supports the Token Status List Draft 14 specification while maintaining existing support for Draft 3. * **Stronger cryptography and standards alignment**: Updated COSE algorithms (as per RFC 9864) strengthen cryptographic compatibility and ensure continued compliance with evolving standards. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency across mobile environments, and improve overall user experience. For a detailed list of changes included in this release, refer to the [SDK Changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log). ## Breaking Changes [#breaking-changes] | # | Element | Change | Impact | | - | ------------------------------------------------------ | ------------------------------------------------------------------------------- | ---------------------------------------------------------- | | 1 | `initialize()` | Now accepts options and returns a `Result` type. | All call sites must handle the result and possible errors. | | 2 | `sendProximityPresentationRequest` | Option renamed from `skipStatusCheck` to `checkStatus` with inverted semantics. | All call sites using `skipStatusCheck` must be updated. | | 3 | `ProximityPresentationSessionTerminationErrorType` | New `Exception` value added. | Exhaustive switches must handle new case. | | 4 | NFC error listener in `registerForNfcDeviceEngagement` | Now routes parse failures to `onError` instead of rethrowing. | Update error handling logic accordingly. | ## New Additions [#new-additions] ### Functions [#functions] | Function | Description | Platform | | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | `fetchAppleWalletConfiguration(options)` | Fetches Apple Wallet configuration needed before initiating the Apple Wallet verification flow. Returns an `AppleWallet` object. | iOS only | | `handleDeepLink(options)` | Handles a deep link URL to continue the online presentation flow. | iOS only | | `requestMobileCredentials(options)` | Remote app-to-app credential verification via Digital Credential Manager / OID4VP. Returns `OnlinePresentationSessionResult`. | Android (DCM/OID4VP), iOS (OID4VP only) | | `destroy()` | Destroys the verifier SDK instance. Returns an error if called while the SDK is initialized; deinitialize the SDK before calling. | All | | `getCurrentLogFilePath()` | Returns path to the Verifier SDK log file. | All | ### Updated Function Signatures [#updated-function-signatures] * `initialize(options?)` — new `InitializeOptions` parameter: * `loggerConfiguration?: LoggerConfiguration` — `logLevel`, `callbackLogLevel`, optional `logDir`, optional `callback` * `platformConfiguration?: PlatformConfiguration` — `tenantHost: string` (required for `requestMobileCredentials`) ### New initialize Options [#new-initialize-options] * `loggerConfiguration?: LoggerConfiguration` — configure SDK logging: logLevel, callbackLogLevel, logDir, callback on log events. * `platformConfiguration?: PlatformConfiguration` — set MATTR VII tenant host for remote credential requests. ### New Types & Enums [#new-types--enums] * **Online presentation (remote/app-to-app):** * `OnlinePresentationSessionResult` — `{ sessionId, challenge?, mobileCredentialResponse?, error? }` * `OnlinePresentationResultError` — `{ type: OnlinePresentationResultErrorType, message }` * `OnlinePresentationResultErrorType` — `SessionAborted | VerificationError | ResponseError | WalletUnavailable | Unknown` * **Apple Wallet:** * `AppleWallet` — object with `requestMobileCredentials(challenge): Promise>` * `RequestMobileCredentialsWithAppleWalletError` / `RequestMobileCredentialsWithAppleWalletErrorType` * **`requestMobileCredentials` options & errors:** * `RequestMobileCredentialsOptions` — `{ request, applicationId, walletProviderId?, challenge }` * `RequestMobileCredentialsErrorType` * `RequestMobileCredentialsError` * **`handleDeepLink`:** * `HandleDeepLinkOptions` — `{ url: string }` * **`fetchAppleWalletConfiguration`:** * `FetchAppleWalletConfigurationOptions` — `{ request, applicationId, merchantId }` * `FetchAppleWalletConfigurationError` / `FetchAppleWalletConfigurationErrorType` * **`initialize`:** * `InitializeOptions`, `LoggerConfiguration`, `PlatformConfiguration`, `LogLevel` (`Off | Error | Warn | Info | Debug | Verbose`) * `InitializeErrorType` = `SdkInitialized | StorageInitializedInBackground` ### New Error Values [#new-error-values] | Location | New values | | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MobileCredentialVerifierErrorType` | `Connectivity`, `StorageInitializedInBackground`, `SdkInitialized`, `PlatformNotSupported`, `PlatformConfigurationInvalid`, `SessionTimedOut`, `SessionAborted`, `DigitalCredentialManager`, `UserCanceled`, `FailedToRequestMobileCredentials`, `AppleWalletNotAvailable` | | `ProximityPresentationSessionTerminationErrorType` | `Exception` | ## Minimum Requirements [#minimum-requirements] **iOS** * iOS 15+ for core SDK functionality. **Android** * Android 7 / Nougat / API 24. * The underlying Android Verifier SDK is built using Kotlin 2.0. This adds some intrinsic dependencies into your build tools. * Kotlin 2.0 is supported from AGP version [8.5](https://developer.android.com/build/kotlin-support). * AGP 8.5 is supported from Gradle version [8.7](https://developer.android.com/build/releases/about-agp) and Android Studio Koala [2024.1.1](https://developer.android.com/studio/releases). ## Migration Steps [#migration-steps] ### Update `initialize()` usage [#update-initialize-usage] The `initialize()` function now accepts an optional options object and returns a `Result`. Update your code to handle the result and possible errors: ```diff - await initialize(); + const result = await initialize(options); + if (result.isErr()) { + // Handle error: result.error + } ``` ### Update `sendProximityPresentationRequest` calls [#update-sendproximitypresentationrequest-calls] The `skipStatusCheck` parameter has been renamed to `checkStatus` with inverted semantics. When `checkStatus` is `true` (default), the SDK will verify credential status. When `false`, it will skip status checking. Update all calls accordingly: ```diff - const response = await sendProximityPresentationRequest({ ..., skipStatusCheck: true }); + const response = await sendProximityPresentationRequest({ ..., checkStatus: false }); ``` | Old Parameter | New Parameter | Mapping | | ----------------------------------- | ------------------------------ | ------------------ | | `skipStatusCheck = false` (default) | `checkStatus = true` (default) | No change needed | | `skipStatusCheck = true` | `checkStatus = false` | Invert the boolean | ### Handle new `ProximityPresentationSessionTerminationErrorType.Exception` case [#handle-new-proximitypresentationsessionterminationerrortypeexception-case] If you switch over `ProximityPresentationSessionTerminationErrorType`, add handling for the new `Exception` value. ### Update NFC error handling [#update-nfc-error-handling] The NFC error listener in `registerForNfcDeviceEngagement` now routes parse failures to `onError` instead of rethrowing. Update your error handling logic accordingly. # Remote verification URL: /docs/verification/remote-overview Remote verification allows verifiers to check the validity of a credential without the need for a physical presence. This is particularly useful for scenarios where the verifier and holder are not in the same location, such as online transactions or remote identity verification processes. Remote verification is available for the following credential formats: * [mDocs](#mdocs) ## mDocs [#mdocs] The [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) specification establishes interoperable methods for remote presentation and verification of mDocs such as mobile driver’s licenses (**mDLs**) and other digital credentials. ### Verification requests [#verification-requests] The [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) specification defines two key aspects of remote verification requests: 1. **Wallet interaction**: Defines how the request and response are transferred between the verifier and the user's wallet: * HTTP Redirects: Standard HTTP redirects are used to pass information between the verifier and the wallet, typically via a web browser. * [Digital Credentials API (DC API)](/docs/verification/remote-web-verifiers/dc-api/overview): A browser-integrated API for digital credential interactions. The DC API allows web apps to communicate securely and seamlessly with wallet apps, improving user experience by eliminating multiple browser redirects. 2. **Request type**: Defines how the verifier requests the credential from the user's device and how should the device respond: * Device retrieval: Based on the [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) standard and defined in ISO/IEC 18013-7 Annex A. The verifier directly requests a credential from the user's device, and the device retrieves and presents the necessary credential data in response. * [OID4VP (OpenID for Verifiable Presentations)](/docs/verification/oid4vp): Based on the [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) protocol and defined in ISO/IEC 18013-7 Annex B. The verifier sends an authentication request, and the device responds with a verifiable presentation containing the requested data. #### ISO/IEC 18013-7:2025 Annexes [#isoiec-18013-72025-annexes] ISO/IEC 18013-7:2025 defines specific annexes for each combination of wallet interaction and request type: | Annex | Request type | Transfer method | Platform support | | ----- | ---------------- | ----------------------- | ------------------------------------------------------- | | A | Device Retrieval | HTTPs | General purpose, browser-based interactions | | B | OID4VP | HTTPs Redirects | General purpose, browser-based interactions | | C | Device Retrieval | Digital Credentials API | Supported on **iOS** devices and the **Safari** browser | The next iteration of ISO/IEC 18013-7 is expected to define an additional Annex D: | Annex | Request type | Transfer method | Platform support | | ----- | ------------ | ----------------------- | ---------------------------------------------------------------- | | D | OID4VP | Digital Credentials API | Supported on **Android** devices and **Chromium-based** browsers | #### Apple Verify with Wallet [#apple-verify-with-wallet] In addition to the protocols defined in the ISO/IEC 18013-7 specification, Apple has introduced the proprietary [Verify with Wallet API](https://developer.apple.com/wallet/get-started-with-verify-with-wallet/). This API can be used to streamline the verification process on iOS same-device flows by allowing direct communication between the verifier and the wallet apps. ### Verification flows [#verification-flows] The [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) specification defines different flows for requesting and presenting mDocs based on the type of verifier application and the responding device: * **Verifier web applications:** Typically use HTTP redirects or the Digital Credentials API to invoke the wallet from a desktop or mobile browser. Web applications support same-device and cross-device flows: * **Same-device flow:** You start the verification experience in a mobile browser and are redirected to a wallet app (where your mDoc is stored) installed on the same mobile device to respond to the request. For example, an online banking portal accessed from your mobile browser prompts you to open your wallet app on the same phone to present your mDoc for identity verification. * **Cross-device flow:** You start the verification experience in a desktop browser and use your mobile device (where your mDoc is stored in a wallet app) to respond to the verification request. For example, you visit a government tax website on your desktop computer, and scan a QR code with your mobile wallet app to present your mDoc. * **Verifier mobile applications:** Can use platform capabilities or app-to-app communication to initiate and complete the verification. Similar to web applications, mobile applications support both same-device and cross-device flows: * **Same-device flow:** You start the experience on a mobile app, and are redirected to a wallet app installed on the same device to present the credential. For example, a tax filing mobile app redirects you to your wallet app on the same phone to verify your identity with your mDoc before filing your return. * **Cross-device flow:** You start the experience on a **mobile app**, but use a **different mobile device** to present the credential. This may happen if your credential is only available on another device you own. For example, you use your tablet to access a government service app, but the app enables you to scan a QR code using your phone where the required mDoc is available in a wallet app. The exact protocols and flows used depend on the requesting application and the user's wallet. Different wallets and browsers support different combinations of these request types and transfer methods. ### Verification channels [#verification-channels] #### Verifier mobile applications [#verifier-mobile-applications] The following table details the different verification channels for mobile applications, including supported flows, supported wallets, underlying protocols and MATTR support status: | Flow | Wallet | Protocol | MATTR support | | -------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------- | ------------- | | Same-device | Apple | Apple's Verify with Wallet | GA | | Same-device | Compliant 3rd party iOS apps | ISO 18013-7 Annex B | GA | |
  • Same-device
  • Cross-device
|
  • Google
  • Samsung
  • Compliant 3rd party Android apps
| ISO 18013-7 Annex D (Draft) | Preview | ### Verifier web applications [#verifier-web-applications] The following table details the different verification channels for web applications, including supported flows, supported wallets, underlying protocols and MATTR support status: | Flow | Wallet | Protocol | MATTR support | | -------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------- | ------------- | |
  • Same-device
  • Cross-device
|
  • Apple
  • Compliant 3rd party iOS apps
| ISO 18013-7 Annex C | Preview | |
  • Same-device
  • Cross-device
|
  • Samsung
  • Compliant 3rd party apps
| ISO 18013-7 Annex B | GA | |
  • Same-device
  • Cross-device
|
  • Google
  • Samsung
  • Compliant 3rd party Android apps
| ISO 18013-7 Annex D (Draft) | Preview | ### Verification checks [#verification-checks] The following standard checks are performed on all mDocs verification requests: * Issuer IACA is valid as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Credential was issued by a trusted issuer (by checking the issuer's IACA against a [local](/docs/verification/remote-verification-api-reference/trusted-issuers#create-a-trusted-issuer) or [external](/docs/digital-trust-service) list of trusted issuers). * Digital signature is valid. * Credential structure complies with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). The following checks are optional and defined as part of the verification request: * Current time is after the beginning of the credential validity period. * Current time is not after the end of the credential validity period. * Credential has not been revoked. # Remote verification implementation guidelines URL: /docs/verification/remote-verification-implementation-guidelines ## Overview [#overview] MATTR provides verification capabilities through both the MATTR Pi Verifier Web SDK and Verifier Mobile SDK, enabling relying parties to seamlessly request and verify verifiable credentials within their own applications. This short guide explains the recommended implementation pattern for integrating the Verifier SDK, including the typical flow and its benefits. It also highlights a practice to avoid, so you can design a solution that is both technically robust and delivers a smooth, trusted experience for users. ## Recommended implementation pattern [#recommended-implementation-pattern] The recommended design principle is to integrate the Verifier Mobile/Web SDK directly into the relying party’s web or mobile applications. This ensures that verification requests are trusted by wallets, and the user experience remains consistent and secure. ### Typical flow [#typical-flow] 1. User interacts with your relying party web or mobile application. 2. Application requests a credential presentation via the Verifier SDK. 3. Wallet validates the origin of the request against its trusted relying party list (e.g., web domain or mobile package ID). 4. Credential is presented back to your application. 5. Verifier SDK verifies the credential. 6. Interaction continues seamlessly. ### What this achieves [#what-this-achieves] * **Trust and compliance**: OEM wallets (Apple, Google, Samsung) enforce strict origin checks, ensuring only registered relying parties can request credentials from these wallet applications. Third-party wallets (such as government-issued wallets) are also likely to adopt similar controls for both security and commercial reasons. * **Smooth user experience**: Users never leave your application, keeping the interaction intuitive and consistent. * **Future-proof integration**: Embedding the SDK avoids wallet validation failures and ensures your solution remains interoperable as wallet ecosystems evolve. ## Best practices [#best-practices] ### Avoid mediated verification windows [#avoid-mediated-verification-windows] An alternative pattern sometimes considered is to redirect the user to a mediated or intermediate window where the SDK is embedded, perform the verification there, and then redirect the user back to the relying party application. This approach should be avoided for two reasons: 1. Technical incompatibility: OEM and third-party wallets will reject credential requests from applications that are not directly registered as relying parties, making mediated flows unreliable. 2. Poor user experience: Redirecting users between apps or browsers fragments the journey, creates confusion, and increases the risk of dropouts. Mediated flows can lead to failed integrations, blocked wallet access, and lost opportunities for credential-based interactions. # Configuration URL: /docs/digital-trust-service/rical-api-reference/configuration ## Update a RICAL configuration [#update-a-rical-configuration] ## Retrieve a RICAL configuration [#retrieve-a-rical-configuration] ## Delete RICAL configuration [#delete-rical-configuration] # DTS root and intermediate CA certificates URL: /docs/digital-trust-service/rical-api-reference/dts-root-ca-certificates ## Create DTS root CA certificate [#create-dts-root-ca-certificate] ## Retrieve all DTS root CA certificates [#retrieve-all-dts-root-ca-certificates] ## Retrieve all DTS root CA Certificates (public) [#retrieve-all-dts-root-ca-certificates-public] ## Retrieve a DTS root CA certificate [#retrieve-a-dts-root-ca-certificate] ## Update a DTS root CA certificate [#update-a-dts-root-ca-certificate] ## Delete a DTS root CA certificate [#delete-a-dts-root-ca-certificate] ## Retrieve a DTS root CA certificate Certificate Revocation List (CRL) (public) [#retrieve-a-dts-root-ca-certificate-certificate-revocation-list-crl-public] ## Create a DTS intermediate CA certificate [#create-a-dts-intermediate-ca-certificate] ## Retrieve all DTS intermediate CA certificates under a root CA [#retrieve-all-dts-intermediate-ca-certificates-under-a-root-ca] ## Retrieve a DTS intermediate CA certificate [#retrieve-a-dts-intermediate-ca-certificate] ## Update a DTS intermediate CA certificate [#update-a-dts-intermediate-ca-certificate] ## Delete a DTS intermediate CA certificate [#delete-a-dts-intermediate-ca-certificate] # General URL: /docs/digital-trust-service/rical-api-reference/general ## Create a RICAL [#create-a-rical] ## Retrieve all RICALs [#retrieve-all-ricals] ## Retrieve a RICAL [#retrieve-a-rical] ## Retrieve latest RICAL [#retrieve-latest-rical] # Signers URL: /docs/digital-trust-service/rical-api-reference/signers ## Create a RICAL signer [#create-a-rical-signer] ## Retrieve all RICAL signers [#retrieve-all-rical-signers] ## Retrieve a RICAL signer [#retrieve-a-rical-signer] ## Update a RICAL signer [#update-a-rical-signer] ## Delete a RICAL signer [#delete-a-rical-signer] # Configuration URL: /docs/digital-trust-service/vical-api-reference/configuration ## Update a VICAL configuration [#update-a-vical-configuration] ## Retrieve VICAL configurations [#retrieve-vical-configurations] ## Delete VICAL configuration [#delete-vical-configuration] # DTS root and intermediate CA certificates URL: /docs/digital-trust-service/vical-api-reference/dts-root-ca-certificates ## Create DTS root CA certificate [#create-dts-root-ca-certificate] ## Retrieve all DTS root CA certificates [#retrieve-all-dts-root-ca-certificates] ## Retrieve all DTS root CA Certificates (public) [#retrieve-all-dts-root-ca-certificates-public] ## Retrieve a DTS root CA certificate [#retrieve-a-dts-root-ca-certificate] ## Update a DTS root CA certificate [#update-a-dts-root-ca-certificate] ## Delete a DTS root CA certificate [#delete-a-dts-root-ca-certificate] ## Retrieve a DTS root CA certificate Certificate Revocation List (CRL) (public) [#retrieve-a-dts-root-ca-certificate-certificate-revocation-list-crl-public] ## Create a DTS intermediate CA certificate [#create-a-dts-intermediate-ca-certificate] ## Retrieve all DTS intermediate CA certificates under a root CA [#retrieve-all-dts-intermediate-ca-certificates-under-a-root-ca] ## Retrieve a DTS intermediate CA certificate [#retrieve-a-dts-intermediate-ca-certificate] ## Update a DTS intermediate CA certificate [#update-a-dts-intermediate-ca-certificate] ## Delete a DTS intermediate CA certificate [#delete-a-dts-intermediate-ca-certificate] # General URL: /docs/digital-trust-service/vical-api-reference/general ## Create a VICAL [#create-a-vical] ## Retrieve all VICALs [#retrieve-all-vicals] ## Retrieve a VICAL [#retrieve-a-vical] ## Retrieve latest VICAL [#retrieve-latest-vical] # Signers URL: /docs/digital-trust-service/vical-api-reference/signers ## Create a VICAL signer [#create-a-vical-signer] ## Retrieve all VICAL signers [#retrieve-all-vical-signers] ## Retrieve a VICAL signer [#retrieve-a-vical-signer] ## Update a VICAL signer [#update-a-vical-signer] ## Delete a VICAL signer [#delete-a-vical-signer] # Core capabilities URL: /docs/concepts/cwt/core-capabilities ## Compactness [#compactness] The compact nature of the credential is an essential feature of offering credentials that work in offline or paper-based contexts, as the credential payload is small enough to fit inside a QR code. ## Multi-Format [#multi-format] CWT credentials can be converted to a number of formats. This includes PDF documents that can be presented digitally or printed, as well as Apple and Google passes that can be stored in a digital wallet. Thus, CWT credentials reduce the digital divide by supporting offline or paper-based contexts alongside digital-first channels. ## Self contained [#self-contained] CWT credentials are bearer credentials where all the assurance is fully self-contained within the QR code, with no need for complex dynamic presentation capability from the holder. ## Offline verification [#offline-verification] Holders present CWT credentials by showing a verifier a QR code representing their credential. This can be scanned visually, or digitally transmitted by uploading an image or PDF. Verification can be online via API or entirely offline. This makes CWT credentials ideal for rapid verification scenarios and/or when there is no reliable internet connectivity. ## Revocation [#revocation] You can use MATTR VII to control the status of issued CWT credentials. This allows verifiers to obtain the revocation status of a CWT credential as it is being presented in a way that preserves the privacy of the credential holder. When a verifier requests a revocation list, the issuer cannot tell what credential are they checking the status for. This means the issuer does not know how often or to whom a credential is being presented, maintaining holder's privacy. # Data structure URL: /docs/concepts/cwt/data ## Overview [#overview] CWT credentials are represented as QR codes, and as a result their payload size and complexity are limited by the size of the generated QR code. To maximize content, CWT credentials are signed using a [Concise Binary Object Representation (CBOR)](https://cbor.io/) data format called [CBOR Web Token (CWT)](https://datatracker.ietf.org/doc/html/rfc8392). This data format is used to represent credentials that become cryptographically verifiable when signed by an issuer. CBOR is a binary data format derived from JSON and allows utilizing data types like numbers, strings & arrays. Being binary, it offers a much more compact message size. Often when CBOR is being discussed or documented, we can represent the same data using JSON to simplify data viewing and modelling. [JSON Object Signing & Encryption (JOSE)](https://datatracker.ietf.org/wg/jose/charter/) is used to create ubiquitous JSON Web Tokens (JWT) in the signed (JWS) and encrypted (JWE) flavours. Similarly, [CBOR Object Signing & Encryption (COSE)](https://datatracker.ietf.org/doc/html/rfc8152) is a general-purpose digital signature encoding format that supports several different cryptographic algorithms. It can be used to create CBOR Web Tokens (CWT) in corresponding CWS (signed) and CWE (encrypted) formats. CWT credentials issued by the MATTR platform are CWTs that have been cryptographically signed using an issuer DID controlled by the tenant. This DID must be a [`did:web`](/docs/concepts/dids#didweb) using a `P-256` key type. Our experience shows that DIDs of this type make offline verification easier by using standard caching mechanisms and better support for the cryptographic primitives. ## Claims [#claims] Information included in the credential is referred to as claims. CWT and Semantic CWT Credentials can include two types of claims: * **Standard claims**: These claims reference specific types of information such as the issuer of the credential and its validity period. * **Custom claims**: These can be any type of claims you wish to include in the credential. The value of any custom claim must be either a boolean, a string or a number. The following example is a signed CWT credential returned by the [MATTR VII sign endpoint](/docs/issuance/direct-issuance-api-reference/cwt-issuance#sign-a-cwt-credential): ```json title="Signed CWT credential" { "id": "bKcrxojFSuSZvI5qhKInxA", "decoded": { "iss": "did:web:learn.vii.au01.mattr.global", "nbf": 1704099600, "type": "Course Credential", "exp": 1767258000, "name": "Emma Jane Tasma", "code": "HS.278", "certificationName": "Working at Heights", "certificationLevel": "Level 4", "issuerName": "Advanced Safety Training", "status": { "index": 3, "url": "https://learn.vii.au01.mattr.global/v2/credentials/compact/revocation-lists/887cd140-e4d7-4518-b70f-305b23778848" }, "jti": "bKcrxojFSuSZvI5qhKInxA" }, "encoded": "CSC:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSALWVQAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQHIAACAACOFBW65LSONSSAQ3SMVSGK3TUNFQWYBA2NFLDPEDENZQW2ZLPIVWW2YJAJJQW4ZJAKRQXG3LBMRRW6ZDFMZEFGLRSG44HCY3FOJ2GSZTJMNQXI2LPNZHGC3LFOJLW64TLNFXGOIDBOQQEQZLJM5UHI43SMNSXE5DJMZUWGYLUNFXW4TDFOZSWYZ2MMV3GK3BAGRVGS43TOVSXETTBNVSXQGCBMR3GC3TDMVSCAU3BMZSXI6JAKRZGC2LONFXGOZTFPBYGS4TZNIZDAMRWFUYDCLJQGE5AAAIAACRAEAYDPB2WQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UF5ZGK5TPMNQXI2LPNYWWY2LTORZS6OBYG5RWIMJUGAWWKNDEG4WTINJRHAWWENZQMYWTGMBVMIZDGNZXHA4DIOAH3BAFA3FHFPDIRRKK4SM3ZDTKQSRCPRCYIA7RFUZYQI3RIGDHIGLAODJ6K2F245DTLIIKXAD35TORFQ7MVRJCIEPH6SC6NGA4HRMK76H5V6GXP66FFNX7MNYC6MYVU7ZLLXYVLXBU" } ``` * `id` : Unique identifier of this CWT credential. This is the `jti` claim, which is the CBOR Web Token (CWT) identifier detailed below. * `decoded` : Includes both standard and custom claims. In the above example `iss`, `nbf`, `type`, `exp`, `status` and `jti` are standard claims, while all other properties are custom claims (`name`, `code`, `certificationName`, `certificationLevel` and `issuerName`). * `encoded` : This is a [CBOR Web Token (CWT)](https://www.rfc-editor.org/rfc/rfc8392.html), which is the decoded object represented as an encoded string. The `CSC:/1` prefix indicates the CWT scheme (COSE Sign Compact profile, CSC) and the version (1). The following claim types are predefined in the [CWT standard](https://www.rfc-editor.org/rfc/rfc8392.html#page-5) to support interoperability. You should only use predefined claims for their intended purpose. Attempting to use any of the predefined claims names as the name of a custom claim that represents a different data type will result in an error. * `iss` (Issuer): Represents the party who issued the credential. This a required field for both CWT and Semantic CWT credentials. Its decoded value must be a `did:web` using a `P-256` key type that must be publicly available on the tenant. * `sub` : (Subject): Represents the subject of the credential. When present, must be a text string. * `kid` (Key Identifier): References the signing key material. It must be present in the protected header section. Its value is used with the `iss` value internally by MATTR VII to resolve the referenced public key (`iss#kid`) and verify the signature of the COSE payload. * `alg` (Signature Algorithm): Represents what algorithm is used for creating the signature. It must be present in the protected header section and must be `ES256`. Its value is used internally by MATTR VII to verify the signature of the COSE payload. * `exp` (Expiration time): Represents the date and time after which the credential is considered expired by the issuer. When present, must be a timestamp encoded as an integer in the `NumericDate` format as specified in [RFC8392](https://tools.ietf.org/html/rfc8392) section 2. * `nbf` (Not Before): Represents the earliest date and time at which the credential is considered valid by the issuer. When present, must be a timestamp encoded as an integer in the `NumericDate` format as specified in [RFC8392](https://tools.ietf.org/html/rfc8392) section 2. * `iat` (Issued At): Represents the credential issuance time and date. When present, must be a timestamp encoded as an integer in the `NumericDate` format as specified in [RFC8392](https://tools.ietf.org/html/rfc8392) section 2. * `cti` (CWT ID): Represents a unique credential identifier as a byte string. Since the decoded object is represented as a JWT, the `cti` claim is mapped to the equivalent JWT `jti` claim, presented as a string. The decoded `jti` value is encoded with `base64URL` without padding. * `status` (Status): Provides information required to discover the current revocation status of the credential. * `type` (Type): Represents the unique type of this credential. When present, its value must be a text string. MATTR highly encourages including a type claim for issued credentials to enable various capabilities in consuming workflows, such as creating and validating [Ecosystem policies](/docs/digital-trust-service/vical-guide#manually-publish-a-vical). ### Semantic CWT credentials claims [#semantic-cwt-credentials-claims] Custom claims are embedded in Semantic CWT credentials into a standard `vc` property. This provides semantic context to the claims and is also compliant with the [W3C Verifiable Credentials Data Model 1.0](https://www.w3.org/TR/vc-data-model). The following is an example of a signed Semantic CWT credential with custom claims inside a vc object: ```json title="Signed Semantic CWT credential" { "id": "bKcrxojFSuSZvI5qhKInxA", "decoded": { "iss": "did:web:learn.vii.au01.mattr.global", "nbf": 1516239022, "type": "Course Credential", "exp": 1516239922, "jti": "urn:uuid:cc599d04-0d51-4f7e-8ef5-d7b5f8461c5f", "vc": { "@context": ["https://www.w3.org/2018/credentials/v1"], "type": ["VerifiableCredential"], "credentialSubject": { "givenName": "Emma Jane", "familyName": "Tasma", "dob": "1979-04-14" }, "status": { "index": 3, "url": "https://learn.vii.au01.mattr.global/v2/credentials/compact/revocation-lists/887cd140-e4d7-4518-b70f-305b23778848" } }, "encoded": "CSC:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSALWVQAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQHIAACAACOFBW65LSONSSAQ3SMVSGK3TUNFQWYBA2NFLDPEDENZQW2ZLPIVWW2YJAJJQW4ZJAKRQXG3LBMRRW6ZDFMZEFGLRSG44HCY3FOJ2GSZTJMNQXI2LPNZHGC3LFOJLW64TLNFXGOIDBOQQEQZLJM5UHI43SMNSXE5DJMZUWGYLUNFXW4TDFOZSWYZ2MMV3GK3BAGRVGS43TOVSXETTBNVSXQGCBMR3GC3TDMVSCAU3BMZSXI6JAKRZGC2LONFXGOZTFPBYGS4TZNIZDAMRWFUYDCLJQGE5AAAIAACRAEAYDPB2WQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UF5ZGK5TPMNQXI2LPNYWWY2LTORZS6OBYG5RWIMJUGAWWKNDEG4WTINJRHAWWENZQMYWTGMBVMIZDGNZXHA4DIOAH3BAFA3FHFPDIRRKK4SM3ZDTKQSRCPRCYIA7RFUZYQI3RIGDHIGLAODJ6K2F245DTLIIKXAD35TORFQ7MVRJCIEPH6SC6NGA4HRMK76H5V6GXP66FFNX7MNYC6MYVU7ZLLXYVLXBU" } } ``` * `jti` : Must be a valid URI according to the [W3C Verifiable Credentials Data Model 1.0](https://w3c.github.io/vc-data-model). To meet these requirements the decoded form of the `cti` claim must be a UUID. # Implementation considerations URL: /docs/concepts/cwt/implementation We have developed the following customer and user experience implementation considerations through our experience with how these work in real-world situations. Even though things may change from use case to use case, these considerations offer flexibility. The following sections provide guidance on how to mitigate common scenarios through your solution/implementation: * [Device Support](#device-support---will-my-end-users-device-be-supported): Will my end user’s device be supported? * [Accessibility](#accessibility---how-do-i-make-my-credential-accessible): How do I make my credential accessible? * [Scannability](#scannability---how-big-do-i-need-to-make-the-qr-code): How big do I need to make the QR code? * [Verification](#verification---what-instructions-can-i-provide-for-presenting-credentials-for-verification): What instructions can I provide for presenting credentials for verification? * [Internationalization](#internationalisation---how-can-i-support-or-display-multiple-languages): How can I support or display multiple languages? ## Device Support –
Will my end user’s device be supported? [#device-support---will-my-end-users-device-be-supported] For CWT credential digital passes, we utilize Apple and Google’s built-in digital wallets – Apple Wallet and Google Wallet (formally known as Google Pay). There are some ifs and buts – however, if your user base is mostly on the latest OS’s supported list, you should be covered. That being said, it is highly recommended to also implement CWT credential PDFs. Having both digital passes and PDFs available provides your user choices – perhaps they don’t want to have a digital pass, perhaps they don’t have a smartphone, perhaps they would prefer to have a backup hardcopy on hand… the list goes on. ### For iOS users (Apple Wallet) [#for-ios-users-apple-wallet] * iOS 10.0 or higher. * Have Apple Wallet app installed. ### For Android users (Google Wallet) [#for-android-users-google-wallet] * Android 5.0 (Lollipop) or higher. * Play Protect certified. * Have Google Wallet app installed and signed in with a Google account. ### Huawei/Honor users and non Play Protect certified devices [#huaweihonor-users-and-non-play-protect-certified-devices] Some Huawei and Honor Android phones are not able to access Google services due to a trade ban - this includes the Google Pay app. Phones that are launched after 15 May 2019 are affected by this long-term issue across the industry. If you have significant user base with these phones, potential workarounds include: MATTR does not endorse any of the apps mentioned below or provide support for the following methods. Please do your own testing to see if this is an acceptable workaround for your users. * Ask end-users to save the PDF on the phone or take a screenshot of the QR code – this allows for offline usage and quicker access. * Ask end-users to click “Add to Apple Wallet” and import the downloaded `.pkpass` file to a supported wallet (such as [Wallet Passes](https://walletpasses.io/) or [PassWallet](https://passwallet.net/)). ## Accessibility –
How do I make my credential accessible? [#accessibility---how-do-i-make-my-credential-accessible] ### PDF [#pdf] We’ve designed our PDF generator to use the same read-order and alt text as provided in your template files. Alt text for dynamic text (text inside form fields) are defined in the [config.json file](/docs/issuance/cwt-credential-templates/pdf-templates#configjson). For in-depth details on how to create an accessible PDF see [Adobe’s Guide](https://helpx.adobe.com/indesign/using/creating-accessible-pdfs.html). ### Apple digital pass [#apple-digital-pass] Apple Pass does not currently support image alt text, so if you are adding text inside any images make sure those aren’t the only instance it gets displayed. If the hero image includes the credential name, perhaps also include it in the header and description field. See [Design an Apple digital pass template](/docs/issuance/cwt-credential-templates/apple-templates) for field customizations. #### Voiceover read order [#voiceover-read-order]
Credential
1. `Advanced safety training` (pulled from `organizationName`) 2. `Certification, H S two hundred and seventy eight` 3. `Registered architects board` 4. `Name, tasma emma` (if `headerFields` are not present then the `primaryFields` are read instead) 5. `ticket fix` 6. `summer waves` #### Voiceover read order [#voiceover-read-order-1]
Credential
1. `Certification, H S two hundred and seventy eight` 2. `Name, emma jane tasma` 3. `Certified for, level four` 4. `E X P, the first of january two thousand and twenty six, barcode, image` ### Google digital pass [#google-digital-pass] Google Pass supports alt text for logo image, hero image, and QR code. Use the `description` key and value to define alt text. Read order is left to right and top to bottom. See [Design a Google digital pass template](/docs/issuance/cwt-credential-templates/google-templates) for field customizations. ## Scannability –
How big do I need to make the QR code? [#scannability---how-big-do-i-need-to-make-the-qr-code] ### PDF [#pdf-1] PDF QR code size can be defined as big or as small as you want. Depending on how big the credential payload is, you might need to make the QR code bigger. We recommend starting at 33 mm (1.3 in) and adjust bigger/smaller depending on space available and testing scannability with the encoded payload (whether the camera app can scan the QR code). We recommend an absolute minimum size of 30 mm (1.25 in).
Barcode
Generated QR codes would have a \~1 mm (\~0.04 in) spacing around all 4 sides, so adjust your `qrCode` form field with that in mind. ### Apple digital pass [#apple-digital-pass-1] Apple Pass’s QR code is fixed and can’t be made bigger or smaller. It is worth noting that even though it looks small in size, if the QR code is scannable at \~33 mm with a printed PDF, then end users should not have any issues scanning an Apple digital pass. This is due to Apple devices having a higher pixel density and being able to display it at a higher resolution. ### Google digital pass [#google-digital-pass-1] Google Pass’s QR code is fixed and can’t be made bigger or smaller. However, Google designed the Google Pay app to display the QR codes fairly big. Same as Apple digital passes, if the QR code is scannable at \~33 mm with a printed PDF, then the end user should not have any issues scanning a Google digital pass. ## Verification –
What instructions can I provide for presenting credentials for verification? [#verification---what-instructions-can-i-provide-for-presenting-credentials-for-verification] The [following guide](https://www.figma.com/community/file/1120944380828784025) uses the example *Working at Heights* certificate. Design source files are made available for you to swap out images to your own credentials or to tweak the text where needed. ### Digital PDF / Apple digital pass / Google digital pass [#digital-pdf--apple-digital-pass--google-digital-pass]
Upload the original PDF received from the certificate issuer.
Don’t take a screenshot of the PDF zoomed out, instead...
Enlarge the PDF so the QR code is in the centre of the screen, then take the screenshot.
iOS: Screenshot from Apple Wallet
Android: Screenshot from Google Wallet
iOS / Android: You don’t need to show the “details” page, that is for your own records only
### Printed copy [#printed-copy]
Scan your hard copy pass in color, at least 200dpi, and as a PDF or jpg file. Did that not work? Try taking a photo instead...
Take a photo of the QR code, ensuring that it is: In frame, Sharp and clear, Bright
Too far zoomed out. QR code is too small within the picture.
Avoid gloss lamination as it causes glare. Instead, use matte lamination.
Too dark and/or blurry. Try going to a brighter space or turning on the flash.
Out of focus. Try moving your camera back a bit until the QR code looks sharp.
## Internationalization –
How can I support or display multiple languages? [#internationalization---how-can-i-support-or-display-multiple-languages] We currently only offer support for Latin-based languages, however other scripts (such as Cyrillic, Hebrew, Arabic) may still work. ### PDF [#pdf-2] #### Using default fonts [#using-default-fonts] As noted in the [Design a PDF template guide](/docs/issuance/cwt-credential-templates/pdf-templates), when no custom font is specified the default Helvetica font is used. This font uses [Windows-1252 encoding](https://en.wikipedia.org/wiki/Windows-1252) character sets which only support 218 characters in the Latin alphabet. **Full character set** Character sets available for Windows-1252 encoding **Supported languages** English, Irish, Italian, Norwegian, Portuguese, Spanish, Swedish, German, Finnish, Icelandic, French, Faroese, Luxembourgish, Albanian, Estonian, Swahili, Tswana, Catalan, Basque, Occitan, Romansh, Dutch (except the IJ/ij character), and Slovene (except the č character). If your payload contains characters outside of this character set, you must use a custom font that has character support for your languages. #### Using custom fonts [#using-custom-fonts] MATTR VII currently only allows for a 700kb maximum `template.zip` file for PDF templates. This means CJK (Chinese, Japanese and Korean) fonts would generally be too big. If this affects your implementation, please [contact us](http://mattr.global/contact). #### Templates [#templates] You may wish to make your PDF bilingual / trilingual / multilingual. Unlike apps or websites, PDFs cannot display different languages depending on the user’s system language preferences. We recommend two options to address this: * **Display all the languages in a single template**: Real world example of this is a passport. The labels have multiple languages on it, and often so does the disclaimer and informational text. This approach may be useful if your use case requires cross-regional holder and verifiers, and the verifier may not be familiar with the credential itself. * **Use different template files for different languages**: If you are already obtaining the user’s preferred language, you may wish to create multiple templates and switch between them as needed. This is useful if the credential would generally be used within one region or presented remotely. ### Apple digital pass [#apple-digital-pass-2] Generally if the language you want to use is available as a system language on iOS, or if there are no issues displaying characters from your language in iOS, then you should not run into any issues. Apple Pass offers multi-language passes. This means a single `.pkpass` file can include multiple languages. The language that gets displayed to the end user will default to the user’s device system language. To learn more about this see [Apple’s Developer Documentation](https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Creating.html#//apple_ref/doc/uid/TP40012195-CH4-SW54). ### Google digital pass [#google-digital-pass-2] Generally if the language you want to use is available as a system language on Android, or if there are no issues displaying characters from your language in Android, then you should not run into any issues. Like Apple Pass, Google Pass offers multi-language passes. You would need to use the `LocalizedString` API type. See [Google Wallet API reference](https://developers.google.com/wallet/generic/rest/v1/LocalizedString) for more information. # CWT credentials URL: /docs/concepts/cwt CWT credentials are used to represent claims of data that can be cryptographically proven as authentic while being compact enough to fit inside a QR code. They are ideal where high information assurance is required but not high identity assurance about the entity presenting the credential. The following sections will provide you with further information on CWT credentials: * [Core capabilities](/docs/concepts/cwt/core-capabilities): Learn more about the core capabilities enabled when using the CWT credential format. * [Credential types](/docs/concepts/cwt/types): Learn about the two different types (CWT and Semantic CWT credentials) and how to choose the one most suitable for your use case. * [Data structure](/docs/concepts/cwt/data): Learn more about how CWT and Semantic CWT credentials are built, and how to use standard and custom claims. * [Implementation considerations](/docs/concepts/cwt/implementation): Learn about key considerations that should be explored as part of implementing either a CWT or Semantic CWT credential into your ecosystem. * [CWT credentials across MATTR Platforms](/docs/concepts/cwt/mattr): Introduces key capabilities that are available across MATTR platforms when you embed CWT credentials into your ecosystem. CWT credentials can be converted to a number of formats. This includes PDF documents that can be presented digitally or printed, as well as Apple and Google passes that can be stored in a digital wallet. Thus, CWT credentials reduce the digital divide by supporting offline or paper-based contexts alongside digital-first channels. Holders present CWT credentials by showing a verifier a QR code representing their credential. This can be scanned visually, or digitally transmitted by uploading an image or PDF. Verification can be online via API or entirely offline. This makes CWT credentials ideal for rapid verification scenarios and/or when there is no reliable internet connectivity. CWT credentials are bearer credentials, where all the assurance is fully self-contained within the QR code, with no need for complex dynamic presentation capability from the holder. However, this means that the verifier cannot verify the identity of the person holding and presenting the credential. Discount vouchers, physical asset tags and luxury goods verification cards are all examples where having authentic information through the supply chain increases the value of goods and services produced, without relying on the identity of the holder. Therefore, CWT credentials are ideal where high information assurance is required but not high identity assurance about the entity presenting the credential. ## Frequently asked questions [#frequently-asked-questions] ### What is a CWT credential? [#what-is-a-cwt-credential] A CWT credential is a verifiable digital credential built on the CBOR Web Token format. It represents claims that can be cryptographically proven as authentic while remaining compact enough to fit inside a QR code. CWT credentials are designed for use cases that require high information assurance but not high identity assurance about the entity presenting the credential. ### When should I use a CWT credential instead of another credential format? [#when-should-i-use-a-cwt-credential-instead-of-another-credential-format] Use a CWT credential when you need a compact, self-contained, QR-code-friendly credential that can be verified offline and does not require the verifier to confirm the identity of the holder. Typical examples include discount vouchers, physical asset tags, and luxury goods verification cards, where the value lies in the authenticity of the information rather than the identity of the bearer. ### How is a CWT credential presented and verified? [#how-is-a-cwt-credential-presented-and-verified] Holders present CWT credentials by showing a verifier a QR code that represents the credential. The QR code can be scanned visually or transmitted digitally by uploading an image or PDF. Verification can be performed online via API or entirely offline using the credential's cryptographic signature. This makes CWT credentials suitable for rapid verification scenarios and contexts without reliable internet connectivity. ### Are CWT credentials bound to a specific holder? [#are-cwt-credentials-bound-to-a-specific-holder] No. CWT credentials are bearer credentials. All the assurance is contained within the QR code itself, so the verifier can rely on the authenticity of the information but cannot confirm the identity of the person holding and presenting the credential. Where identity assurance is required, an mDoc or another credential format with holder binding is more appropriate. ### Can CWT credentials be used without a smartphone? [#can-cwt-credentials-be-used-without-a-smartphone] Yes. CWT credentials can be converted into a number of formats, including printable PDFs and Apple or Google passes. This supports offline and paper-based contexts alongside digital-first channels, which helps reduce digital exclusion where smartphone access cannot be assumed. # CWT credentials across MATTR platforms URL: /docs/concepts/cwt/mattr When CWT credentials are added to a trust network, they can be applied to a wide variety of use cases and business problems. CWT credentials are currently available across the following MATTR platforms: * **MATTR VII**: Offers a set of APIs that enable the following: * Managing CWT credential configurations that serve as a template for signing and issuing CWT credentials. * Creating CWT credential offers. * Signing CWT credentials using direct API requests, and then delivering to a holder’s digital wallet via a separate endpoint. * Formatting CWT credentials as QR codes, PDF documents, Apple or Google passes. * Creating and managing templates for the different credential formats. * Creating and managing DIDs which are required to issue CWT credentials. * Verifying a signed CWT credential using a direct API request. * **MATTR Pi**: Offers two SDKs to handle CWT credentials: * MATTR Pi Holder SDK: Enables accepting a CWT credential offer and trigger its signing and issuance to the wallet. * MATTR Pi Verifier SDK: Enables verifying a signed CWT credential. * **MATTR GO**: * MATTR GO Wallet: Can be used to claim, hold and present CWT credentials. * MATTR GO Verifier: Can be used to verify presented CWT credentials. # Credential types URL: /docs/concepts/cwt/types MATTR VII supports two types of CWT credentials: * **CWT credentials**: These are based on a concise, non-semantic model. * **Semantic CWT credentials**: These are based on the [W3C Verifiable Credential (VC) data model](https://www.w3.org/TR/vc-data-model/) and provide more descriptive semantic meaning. Semantic CWT credentials offer a standards-compliant way of constructing a verifiable credential that can be understood by many parties in an interoperable way. However, the data model is expressive in ways that will result in payloads significantly larger than the claims data. The smaller the claims, the larger the ratio of the data model overhead. In general, the more compact a credential is, the quicker and easier it is to manage by holders and verifiers. The choice between the two comes down to how compact you need the credential to be versus how openly you intend to share and exchange credentials across different domains and jurisdictions. Therefore, choosing is a balance of standards compliance supporting an open ecosystem or a more compact credential that can probably only be used within a closed ecosystem. # Core Capabilities URL: /docs/concepts/mdocs/core-capabilities The core capabilities enabled by mDocs can be split into two main categories - enhanced security and improved user experience. ## Security features [#security-features] ### Issuer data authentication [#issuer-data-authentication] Relying parties need to be able to know what entity issued a presented credential, as that information can affect their decision to trust information included in the credential. mDocs adhere to a very specific and well-defined suite of data structures, procedures and cryptographic algorithms defined in the ISO 18013-5 standard. This enables relying parties to verify the origin and the issuer of a credential through a chain of linked certificates all the way to its root Certificate Authority (CA). Refer to the [Chain of trust](/docs/concepts/chain-of-trust) sub-section for more information. ### Device authentication [#device-authentication] Relying parties must be confident that a presented credential was in fact issued to the device it is presented from. To protect against malicious cloning, mDocs are bound to a mobile device and enable verifying the binding between a credential and the mobile device used to present it. We refer to this concept as device authentication. When a credential is issued to a mobile device, the latter generates a private key that is locked to its tamper resistant key store. The issuer then includes the corresponding public key in the credential to establish the device binding. That same private key must be used whenever the credential is presented to a relying party. ### Holder authentication [#holder-authentication] Relying parties must also be able to validate that the person presenting the credential is the same person the credential was issued to. For this purpose, mDocs can include a portrait picture of their holder, enabling the verifier to compare it with the person presenting them in person. This comparison can be performed either manually or using facial recognition technologies. While the ISO/IEC 18013-5 standard explicitly requires a portrait picture as part of a valid mDL certificate, our current issuance and verification capabilities do not enforce it. That is because mDocs were built to meet wider use cases, some of which may not require a portrait picture. Advanced techniques enable confirming the authorized person is presenting the credential without sharing their biometric data as part of the verification workflow. ### Session encryption [#session-encryption] mDocs communication protocols establish an end-to-end encrypted channel to ensure the credentials remain hidden and confidential from any possible eavesdroppers. This security feature applies to both in-person and remote (online) verification workflows. Refer to the [Verification](/docs/verification) section for more information. ## User experience [#user-experience] ### Multiple verification workflows [#multiple-verification-workflows] mDocs offer tremendous value as a single credential can be used across different digital interactions and workflows: #### Remote interactions [#remote-interactions] mDocs can be used in online interactions via either same-device or cross-device workflows as per ISO/IEC 18013-7. In same-device workflow, the user completes the entire interaction on their mobile device, whereas in cross-device workflows they begin an interaction on a different device and are then redirected to the mobile device that holds the mDoc to complete the interaction. #### In-person interactions [#in-person-interactions] mDocs are constructed in a way that enables real time offline verification, with no reliance on internet-based technologies. The two devices (holder’s and verifier’s) communicate via BLE and have all the information required to complete a verification workflow using non-internet technologies and communication protocols. This means that in-person verification workflows can be completed anywhere, regardless of location and internet coverage. Refer to the [Verification](/docs/verification) section for more information. ### Selective disclosure [#selective-disclosure] mDocs allow relying parties to specifically request only the information they need from the holders of these credentials. The holders, in turn, can consent to sharing selected information contained in the credential while fully understanding what, with whom, and for what purpose they are sharing it. This concept is known as *Selective Disclosure*. Refer to the [Structure to function](/docs/concepts/mdocs/structure-to-function#salt-values-support-unwanted-disclosures-protection) sub-section for more information on how mDocs enable this capability. ## Revocation [#revocation] mDocs issued using MATTR VII have the capability to be set with a status that can be checked by Verifiers while preserving the privacy of the holder. This status can then be updated by Issuers to indicate if the credential is permanently revoked, temporarily revoked, or still valid. The holder can use their digital wallet to see the latest status of their mDocs at any time. Refer to [Revocation](/docs/issuance/revocation/overview) to learn more about this capability. # mDocs URL: /docs/concepts/mdocs mDocs are based on the [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) standard and [18013-7](https://www.iso.org/standard/91154.html) technical specification. They are digital identity documents designed to be stored on the holder’s mobile device, and can be verified either in-person or remotely (online). The key strength of mDocs over other digital credential technologies lies in their ability to provide strong authentication and strong identification, supporting digital interactions that were previously impossible due to high security risks. By offering increased levels of security for both offline and online verification workflows, mDocs allow for seamless integration into a variety of use cases across industries. They are particularly ideal for high assurance identity credentials such as passports and national identification cards, as they offer added protection against forgery, cloning, eavesdropping and impersonation. Designed to be stored in a digital wallet on a mobile device, mDocs allow for a secure binding between the mobile device and the credential, as well as a tighter native integration with iOS and Android. This means credential verification workflows can leverage close proximity technologies such as Bluetooth Low Energy (BLE). The following sub-sections detail the standards and technologies which mDocs are based on, as well as the key capabilities they offer across MATTR platforms: * [Core Capabilities](/docs/concepts/mdocs/core-capabilities): Reviews the core capabilities available for mDocs, setting the ground for the following subsections who explain how these capabilities are enabled. * [Standards and Technologies](/docs/concepts/mdocs/standards-and-technologies): Describes the ISO/IEC standards mDocs are based on, as well as some key concepts and technologies that would help you better understand mDocs. * [From Structure to Function](/docs/concepts/mdocs/structure-to-function): Reviews the structure of mDocs and mDocs presentations, and how that structure enables the key functionalities offered by mDocs. * [mDocs across MATTR Platforms](/docs/concepts/mdocs/mattr): Introduces key capabilities that are available across MATTR platforms when you embed mDocs into your network. ## Frequently asked questions [#frequently-asked-questions] ### What is an mDoc? [#what-is-an-mdoc] An mDoc is a digital identity document designed to be stored on a holder's mobile device and verified either in person or remotely. mDocs are based on the ISO/IEC 18013-5 standard and the ISO/IEC 18013-7 technical specification. They are particularly suited to high assurance identity credentials such as passports, national identification cards, and mobile driving licenses. ### What is the difference between an mDoc and an mDL? [#what-is-the-difference-between-an-mdoc-and-an-mdl] An mDL (mobile driving license) is a specific type of mDoc that represents a driving license, defined by ISO/IEC 18013-5. mDoc is the broader category of credential format defined by the same standard, which can also be used for other high assurance identity documents such as passports or national identification cards. ### How are mDocs verified? [#how-are-mdocs-verified] mDocs are verified by checking the cryptographic signature applied by the issuer, the binding between the credential and the holder's device, and the credential's status. Verification can be performed in person using close proximity technologies such as Bluetooth Low Energy, or remotely over the internet. Most verification steps can be performed offline because the signature does not require a live lookup against the issuer. ### Are mDocs more secure than other digital credential formats? [#are-mdocs-more-secure-than-other-digital-credential-formats] mDocs are designed for high assurance use cases. They support strong authentication and strong identification through device binding, session encryption, and protections against forgery, cloning, eavesdropping, and impersonation. Other formats may be more appropriate where these protections are not required or where the credential is primarily about information assurance rather than identity assurance. ### Do mDocs support selective disclosure? [#do-mdocs-support-selective-disclosure] Yes. mDocs use a salted hashed claims structure that allows the holder to present only the attributes a verifier needs, without revealing the rest of the credential. This means a verifier can confirm a specific fact, such as proof of age, without learning unrelated personal data. # mDocs across MATTR platforms URL: /docs/concepts/mdocs/mattr When mDocs are added to a trust network, they can be applied to a wide variety of use cases and business problems. mDocs are currently available across the following MATTR platforms: * **MATTR VII**: Offers a set of APIs that enable the following: * Managing [mDoc credential configurations](/docs/issuance/credential-configuration/overview) that serve as a template for signing and issuing mDocs. * Creating [credential offers](/docs/issuance/credential-offer/overview) and issuing mDocs to compliant digital wallets via an OID4VCI [Authorization Code](/docs/issuance/authorization-code/overview) and/or [Pre-authorized Code](/docs/issuance/pre-authorized-code/overview) flows. * Manage the [revocation](/docs/issuance/revocation/overview) status of issued mDocs, including managing the required Status List configurations, Status List signers and Status List signers certificates. * Creating and managing the certificates required to sign mDocs. MATTR VII can be used to manually create Issuing Authority Certificate Authority (IACAs) and is automatically creating Document Signer Certificates (DSCs) when required. Alternatively, MATTR VII can be configured to use [unmanaged certificates](/docs/concepts/chain-of-trust#external-certificates) that are managed and uploaded by the issuer. * **MATTR Pi**: Offers SDKs to handle mDocs holding and verification: * [Holder SDKs](/docs/holding/sdk-overview): Enables claiming mDocs via an OID4VCI workflow and presenting it for verification either in-person or remotely. * [Verifier Mobile SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview): Enables proximity and remote verification of a signed mDoc, as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * [Verifier Web SDK](/docs/verification/remote-web-verifiers/sdks/overview): Enables remote verification of a signed mDoc, as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). * **MATTR GO**: Offers two white-labeled apps that can handle mDocs. * [GO Hold](/docs/holding/go-hold/getting-started) can claim, hold and present mDocs either in-person or remotely. * [GO Verify](/docs/verification/go-verify/getting-started) can perform proximity verification of a signed mDoc, as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). Currently our APIs only support the ECDSA with P-256 (ES256) algorithm for issuer and device authentication. Contact us if you want to better understand the rationale for this choice. # Standards and technology URL: /docs/concepts/mdocs/standards-and-technologies This page details the following mDocs concepts: * [Underlying standards](#underlying-standards): This will help you understand where mDocs are positioned in relation to existing standards. * [Foundational technologies](#foundational-technologies): This will help you understand what technologies are being used to enable the core concepts of mDocs, and how they might interface with your existing ecosystem. ## Underlying standards [#underlying-standards] The [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) standard was created to standardize certain aspects in a Mobile Driver Licenses (mDLs) ecosystem. It defines the interface for a mobile device to present an mDL to a verifier using a digital wallet in a close proximity scenario. This includes data structure and important security features enabling the verifier to validate the mDL. The [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) technical specification was created to augment capabilities defined in the [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html) standard by enabling using a browser to present an mDL to a verifier remotely (online). This standard specifies two protocols to support online presentation of mDLs - RestAPI and [OpenID for Verifiable Presentations (OID4VP)](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). MATTR mDocs implement the latter, which defines a mechanism on top of [OAuth 2.0](https://www.rfc-editor.org/info/rfc6749) to enable online verification of verifiable credentials, including mDLs. The [ISO/IEC TS 23220 standard series](https://www.iso.org/standard/74910.html) (currently under development) aims to generalize applications of this technology for more use cases, broadly referred to as Mobile Documents or mDocs. ISO/IEC TS 23220-4 is compatible with ISO/IEC 18013-5 and ISO/IEC 18013-7 while introducing additional capabilities such as holder authentication. MATTR mDocs have a generic format that can be presented with any claims and can be used in many scenarios - purchasing age-restricted items, opening bank accounts, renting or sharing cars, going through airport security, accessing secure locations and more. ## Foundational technologies [#foundational-technologies] ### Object representation [#object-representation] mDocs verification workflows require compact data structures. Therefore, Concise Binary Object Representation (CBOR) is the main format used to encode mDocs data structures. CBOR is a binary data format designed to be interoperable with JSON while supporting more complex data types. Being binary, it offers more compact messages, and allows encoding data directly without converting it to a base64-encoded string first. In the context of mDocs, this is a big advantage over JSON, which is a less space efficient data encoding method. Refer to [CBOR.io](https://cbor.io/) to go deeper into CBOR. ### Object signing [#object-signing] Personal information included in mDocs must be signed to be tamper proof. CBOR Object Signing and Encryption (COSE) allows signing CBOR data structures in the same way JSON data structures can be signed by JavaScript Object Signing and Encryption (JOSE). mDocs use COSE to sign CBOR data structures, verifying the validity of the signed credential by authenticating both the issuer and the device. ### Hashed salted claims [#hashed-salted-claims] A hash is a cryptographic function that takes in data of any length, and creates a fixed length unique output, referred to as a Hash (also known as a Digest). The algorithm type indicates the fixed output length. For example, the SHA-256 algorithm generates 256 bits / 32 byte long hashes: Hash function Valid hash functions must meet the following requirements: * Collision resistant: Two distinct inputs must never result in the same hash. * One-way: A Hash cannot be used to decipher the original input. * Repeatable: The same data input should always generate the same output hash. A salted hash value is the result of combining a unique random salt with a piece of data (e.g., a claim) and applying a hash function, making it more secure by preventing attackers from easily reversing or guessing the original data. In the context of mDocs, salted hashed claim are used to enhance privacy by allowing holders to reveal only specific information to relying parties, without disclosing unnecessary personal data. This is referred to as selective disclosure. Each claim in the mDoc (e.g., date of birth or address) is hashed with a unique salt and signed by the issuer, making it impossible to reverse-engineer the original data unless the holder explicitly discloses it—even if the relying party receives a credential that contains salted hashes of other undisclosed claims. This enables holders to reveal only the necessary claim, such as their birthdate, without disclosing other claims like their address. For example, if an mDoc contains both a holder's birthdate and address, the credential will contain salted hashes of each. When the holder needs to prove their age, they disclose only the birthdate. The relying party receives the disclosed birthdate, along with its corresponding hash and salt from the credential and can confirm the holder meets the age requirement without accessing other information, such as the address, thereby preserving the holder’s privacy. ### X.509 certificates [#x509-certificates] mDocs are high assurance identity credentials. Verifiers must be able to authenticate their issuers to enable digital trust workflows within the ecosystem. X.509 certificates are a standardized format for digital certificates, fundamental to Public Key Infrastructure (PKI). Each certificate includes a public key and details about the certificate itself, binding this information to a specific entity. Public key validation and verification processes guarantee that the information in the certificate aligns with the claimed entity, maintaining the overall security and trustworthiness of the PKI infrastructure. X.509 certificates commonly use certificate chains which encompass multiple layers of digital certificates to establish a chain of trust. The PKI hierarchy begins with a root certificate, acting as the highest authority that issues intermediate certificates, creating a chain of trust leading to end-entity certificates (such as mDocs). Certificate chain validation ensures the integrity of this hierarchy, confirming that each certificate in the chain is legitimate, signed by its issuer and has not been revoked. In the context of mDocs, an [Issuing Authority Certificate Authority (IACA)](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) and a [Document Signer Certificates (DSC)](/docs/issuance/certificates/overview#document-signer-certificate-dsc) are both X.509 certificates. # Structure to Function URL: /docs/concepts/mdocs/structure-to-function To properly use mDocs in a digital trust ecosystem, one must gain a good understanding of their structure, and how it relates to their function. ## Data structure [#data-structure] ### From claims to an mDoc [#from-claims-to-an-mdoc] mDocs issuers ascertain verifiable information about holders they issue credentials to. This information is referred to as *claims* and is stored in systems which are referred to as *claims sources*. When these claims are issued in an mDoc they are grouped into *namespaces* to prevent collision between claim names. When creating a presentation from an mDoc and sharing it with a verifier, it only includes a subset of the raw claims from the mDoc. These are the elements required by the verifier that the holder had consented to disclose. By comparing these elements to the corresponding salted hashes in the Mobile Security Object (MSO), the verifier can ascertain the claims' integrity. The following diagram depicts the mDoc and Presentation architecture which includes the aforementioned elements:
mDoc * MSO: A COSE\_sign1 structure (CBOR standard for presenting digital signatures) comprising several components: * `Header` : Details the algorithm being used. * `DSC` : The Document Signer Certificate (DSC) is included in the MSO and can be used alongside the IACA certificate to verify the MSO signature. * `Issuer Signature` : The signature of the mDoc’s issuer. * Payload: The element signed by the DSC. It includes the following components: * Device public key. * Credential validity period. * Metadata (key type, authorizations, etc.). * Element Digest/Hash: An array that includes all the salted hashes of issuer signed claims. * `All Elements` : Includes the raw claims grouped by namespaces. Each element within a namespace includes the claim name, claim value and salt value.
### From an mDoc to a presentation [#from-an-mdoc-to-a-presentation] The following diagram depicts what elements are carried through when we creating a presentation from an mDoc and sharing it with a verifier:
mDoc Presentation * `DSC` : Required to verify the MSO signature. * `Selected Elements` : The subset of raw claims from the mDoc. These are the elements required by the verifier and agreed to be shared by the holder. Comparing these elements to the corresponding hashes in the MSO, the verifier can ascertain their integrity. * `Device Auth` : A signature produced using the private key associated with the device public key in the MSO over the unique session data.
## Function [#function] The structure described above serves the following capabilities: ### Salted hashed claims enable selective disclosure [#salted-hashed-claims-enable-selective-disclosure] The MSO is always included in an mDoc presentation to enable validating the issuer signature. Selective disclosure would not be possible if all claims in the MSO were signed then revealed to every verifier. The salted hashing of claims into the MSO ensures that it provides no data and only proves that what is disclosed and presented by the holder was indeed signed by the issuer. When a verifier is validating an mDoc presentation, they re-compute the hash/digest for each revealed claim and its salt, and try to match it to the one referenced in the MSO. If the match is successful then the integrity of the claim back to the issuer is proven. If they don’t match, the value is rejected and the presentation is invalid. This structure enables the holder to selectively disclose a subset of their credential claims, and the verifier to verify this subset of claims. ### Salt values support unwanted disclosures protection [#salt-values-support-unwanted-disclosures-protection] Since hash functions are repeatable, brute forcing might enable verifiers to guess unrevealed claims when their enumeration is very limited (for example nationality can only be a two letter country code defined in [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html)). To solve this, the issuer adds a unique random massive number (commonly referred to as *nonce* or *salt*) to each claim value for every hash that is created. This means you can only reconstruct the hashed claim if you have both the salt and the claim value: Hash and Salt ### Device Auth provides anti-cloning and replay protection [#device-auth-provides-anti-cloning-and-replay-protection] The `Device Auth` element authenticates that the device presenting the credential is the same one that the credential was issued to. When an issuer issues an mDoc, the device generates a private key that is locked to its hardware. That same private key must be used whenever the credential is presented to a verifier, both in-person and remotely (online). Assuming the private key is exclusively bound to a device, it prevents anti-cloning, and the relying party can trust that the credential is both valid and is presented by the intended device. Device authentication can be implemented in one of two methods, both supported by the ISO/IEC 18013-5:2021 specification: * **Signature authentication**: The holder uses the device key to produce a digital signature that is included in the device’s response, authenticating the holder’s possession of the device key to the verifier. This is considered a non-repudiable method. * **ECDH-agreed Mac based authentication**: The holder and the verifier use their own private key and the other parties public key to generate a mutually agreed key using ECDH (Elliptic Curve Diffie Helman). This prevents scenarios where a verifier may attempt to share the response with a 3rd party without the holder’s consent. Since both parties (the holder and the verifier) could generate the same MAC, the verifier cannot prove to a 3rd party that it was the holder who generated the MAC. This method is considered to be non-repudiable to the verifier, but it is repudiable to a 3rd party as it can’t tell whether the MAC was produced by the holder or the verifier. Replay protection is achieved using the same mechanism by signing over the session transcript, acting as a fingerprint of the current session between the verifier and the holder. ### Issuer Auth enables offline verification [#issuer-auth-enables-offline-verification] The mDoc is constructed in a way that the verifier only needs the IACA’s certificate of the issuer to verify a presented credential. Everything else (DSC, MSO) exists within the mDoc itself, enabling offline verification as no internet access is required to retrieve any information. ### Status field enables revocation checking [#status-field-enables-revocation-checking] [Revocable](/docs/issuance/revocation/overview#mdocs) mDocs include a status reference in their MSO payload. The standardized field name is `status`, though legacy credentials issued before adoption of Draft 14 of the Token Status List specification use `_status`. Both field names contain identical information and serve the same purpose. For more details, see [mDocs revocation](/docs/issuance/revocation/overview#mdocs). ## Signed mDoc payload [#signed-mdoc-payload] Let's look into the structure of a signed mDoc issued by a MATTR VII tenant: ```json title="Signed mDoc example" { "id": "8669f836-b3b2-43f3-aecd-d92187cb1cac", "encoded": "omppc3N1ZXJBdXRohEOhASahGCFZApcwggKTMIICOKADAgECAgpSc4u6ewLWeF9jMAoGCCqGSM49BAMCMC4xCzAJBgNVBAYTAkFVMR8wHQYDVQQDDBZsYWJzLm1hdHRybGFicy5jbyBJQUNBMB4XDTI0MDcyOTEwMTMwMVoXDTI1MTAyNzEwMTMwMVowOTELMAkGA1UEBhMCQVUxKjAoBgNVBAMMIWxhYnMubWF0dHJsYWJzLmNvIERvY3VtZW50IFNpZ25lcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIwfV3GMKmD2/AeX0nyM17WWpDcu0ow/l+LwX7JsrvMmGV2IJmkJnCLPlFJS8OEZCH2Awx6dcHZM9ypPWPxRFJ+jggExMIIBLTAdBgNVHQ4EFgQUeDSAUoc4IOG98TfH4oBwEAXzjCEwHwYDVR0jBBgwFoAUjpQIFBA5F7EMBwhiw7HRjumaE/4wDgYDVR0PAQH/BAQDAgeAMCQGA1UdEQQdMBuGGWh0dHBzOi8vbGFicy5tYXR0cmxhYnMuY28wJAYDVR0SBB0wG4YZaHR0cHM6Ly9sYWJzLm1hdHRybGFicy5jbzB4BgNVHR8EcTBvMG2ga6BphmdodHRwczovL2xhYnMudmlpLmF1MDEubWF0dHIuZ2xvYmFsL3YyL2NyZWRlbnRpYWxzL21vYmlsZS9pYWNhcy9mNDU5ZjliMi0xNzZkLTQ5NzAtYjRjYi0xZGFhOGVmOGE0ZTUvY3JsMBUGA1UdJQEB/wQLMAkGByiBjF0FAQIwCgYIKoZIzj0EAwIDSQAwRgIhAKKrbWcL0zSR7v+eMVnhvGOPfYN5Z1N7Z3bwRmfC9UTuAiEA3VS+f1ejLdHkyBKnzUjPBjOHYZ0uFX1vMS/QShgy0mNZ5MXYGFnkwKdndmVyc2lvbmMxLjBvZGlnZXN0QWxnb3JpdGhtZ1NIQS0yNTZsdmFsdWVEaWdlc3RzoXFvcmcuaXNvLjE4MDEzLjUuMbIAWCBHKXMYF97lb0Z04rfrV2P4jXk9xglx/6jKG6j2DciXxgFYIDs6fkgoiSVVbePNCUWpJI6L+rOFssU9ezcU4yNO05wnAlggo4SzZYZ40QSCJ78EpUzn96E+7t4StK9QjHI8hO2AHwMDWCBsMR9aWU8u548e7/ms/o2YTF0e5huk7SE7zaKrI/fDlARYIKHbUNrLFOo0fZ9tp0aWR7R9ivZydJzSiCMuTGFRqp5KBVggtGdZGpki+cVQ7gcwMPso1YPt7zMNrVOSPUxJjaszz7EGWCB1D6PoCZyVXGTsPIz0Vkm3PZisEJB+RerimlCCE8prcAdYILf8HxOxwVwds6KAwXCNta9aYHeqFc+7A8kLgItGmQhXCFggCAA4gbM54lbSlP0CCQ+0Xxzhqhi7lyqMdUNpQG1iOvoJWCBlKg/lHR7Q/tiC0GffuQ65zGcP6gdjvxx6pgUytNoRAgpYIJIrLr3TethAOk2L+H1niDFeh4n9S+lGsQ2TQ2W+L9mHC1gg4ysAT7MLuJGIjfa0aWmKlRvwegYldoRMrWPBa1oZp1UMWCBejb71fRUoVBycI/hb6+d9li8Nt/AGDr4cHqeFgqBNKg1YIEEWtB5rzkDXMcIL589lMjoSke6XEUextEsTR0FjpGfsDlggqhJ91EIJZrTW3ovq5Fc5yX3hl+24F0s2PM+kUXY4y0EPWCBwj8knItIZzL0BOLINHgHAIfh5pkXlftuhcEnFcxY9KxBYINfHE/iNIeoMvp5KXeNBZlfHhHIlEfvjGbsaAx1IwJN3EVggm43zZkO72fc1lwnX/8Q4O1C/BV2CMQBRlf11ZwgIp5RtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5pAECIAEhWCCIwikMm+n28zOyVBGiAr/UOuX81Zgv7i3PzdvwPzG5biJYIDj/fzDqOGd/zfMfj0+xRiMUMX0+Y2VIOVmS66zFm1XxZ2RvY1R5cGV2b3JnLmlzby4xODAxMy41LjEubURMCWx2YWxpZGl0eUluZm+janZhbGlkVW50aWzAdDIwMjQtMTAtMDJUMjI6Mjg6NDBaaXZhbGlkRnJvbcB0MjAyNC0wOS0wMlQyMjoyODo0MVpmc2lnbmVkwHQyMDI0LTA5LTAyVDIyOjI4OjQxWmlfYnJhbmRpbmemZG5hbWVuRHJpdmVyIExpY2Vuc2VrZGVzY3JpcHRpb254MERldGFpbHMgYWJvdXQgeW91ciBhZ2UsIGlkZW50aXR5ICYgbGljZW5zZSBjbGFzc29iYWNrZ3JvdW5kQ29sb3JnIzAwN0VBN253YXRlcm1hcmtJbWFnZaJmZm9ybWF0Y3N2Z2RkYXRhWS0FPHN2ZyB3aWR0aD0iMjQ1IiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDI0NSAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMjEzXzU1MjApIj4KPHBhdGggZD0iTTE1NS4wNjkgNjYuODY3NUwxNDAuNDgyIDU1LjA5NjlDMTQwLjA4MiA1NC43NzQ3IDEzOS44NDggNTQuMjg3NiAxMzkuODQ4IDUzLjc3MjdMMTM5Ljg0OCA0NC42NDc5QzEzOS44NDggNDMuOTg1OCAxNDAuMjM0IDQzLjM4MiAxNDAuODM1IDQzLjEwM0wxNTMuMDQ1IDM3LjQ0MDlDMTUzLjkyNyAzNy4wMzI1IDE1NC4yODggMzUuOTY3IDE1My44MzYgMzUuMTA0NUwxNDkuNzgyIDI3LjM4NTFDMTQ5LjY1MyAyNy4xNDE2IDE0OS41ODcgMjYuODcwMiAxNDkuNTg3IDI2LjU5MzdMMTQ5LjU4NyAyMi41NDVDMTQ5LjU4NyAyMS45NDM3IDE0OS45MDcgMjEuMzg1NyAxNTAuNDI0IDIxLjA3ODdMMTc1LjM3NiA2LjM1MjcyQzE3NS45ODcgNS45OTI1IDE3Ni43NTggNi4wNDgzMSAxNzcuMzExIDYuNDk0NzhMMjAwLjk2NyAyNS42MDE4TDIwMC45NjcgMzguNzA2OEMyMDAuOTY3IDM5LjY0NzkgMjAwLjIwNiA0MC40MDg5IDE5OS4yNjUgNDAuNDA4OUwxODUuODIgNDEuMjQ1M0MxODQuODc5IDQxLjI0NTMgMTg0LjExOCA0Mi4wMDYzIDE4NC4xMTggNDIuOTQ3NUwxODMuMDA5IDU1LjU5NDFDMTgzLjAwOSA1Ni41MzUyIDE4My43NyA1Ny4yOTYyIDE4NC43MTEgNTcuMjk2MkMxODUuNjUyIDU3LjI5NjIgMTg2LjQxMyA1OC4wNTczIDE4Ni40MTMgNTguOTk4NEwxODYuNDEzIDcwLjUyNTVDMTg2LjQxMyA3MS40NjY3IDE4NS42NTIgNzIuMjI3NyAxODQuNzExIDcyLjIyNzdMMTczLjE3NiA3Mi4yMjc3QzE3My4wMTIgNzIuMjI3NyAxNzIuODQ3IDcyLjIwMjMgMTcyLjY4NyA3Mi4xNTY3TDE1NS4wNjYgNjYuODY3NSIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE5Mi44OCA1OC41MjIxTDE5Ni41OTYgNTQuMzMyNEMxOTYuNzY3IDU0LjEzOTYgMTk2Ljg0MyA1My44ODExIDE5Ni44MDUgNTMuNjI2M0wxOTUuNjYxIDQ2LjAxODhDMTk1LjYxIDQ1LjY3NjkgMTk1LjM2MSA0NS4zOTc5IDE5NS4wMjcgNDUuMzA4NUwxOTEuMjk5IDQ0LjMwOTdDMTkwLjgzNSA0NC4xODU0IDE5MC4zNTkgNDQuNDYwNiAxOTAuMjM0IDQ0LjkyNDNMMTg3Ljc4NCA1NC4wNjk2QzE4Ny42NiA1NC41MzMzIDE4Ny45MzUgNTUuMDEgMTg4LjM5OSA1NS4xMzQyTDE5MC4zODYgNTUuNjY2N0MxOTAuNzA0IDU1Ljc1MTkgMTkwLjk0NyA1Ni4wMDk1IDE5MS4wMTMgNTYuMzMyTDE5MS4zNzggNTguMTE5NkMxOTEuNTIyIDU4LjgyMTQgMTkyLjQwNSA1OS4wNTggMTkyLjg4IDU4LjUyMjFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTI2LjAxNiA5Ni4wMDk3TDEyNi40NjggODIuMjYyNUgxMzAuNTM0TDEzMy4xMTYgOTEuMDA3N0gxMzMuMjU2TDEzNS44MjcgODIuMjYyNUgxMzkuODgyTDE0MC4zNDQgOTYuMDA5N0gxMzcuNjEyTDEzNy40ODMgOTEuMDYxNUwxMzcuMzY1IDg1LjM0OTdIMTM3LjE5M0wxMzQuNDkzIDk0LjQwNjlIMTMxLjg2OEwxMjkuMTU3IDg1LjM0OTdIMTI4Ljk4NUwxMjguODY3IDkxLjA3MjNMMTI4Ljc0OSA5Ni4wMDk3SDEyNi4wMTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTQ4LjM0NiA5Ni4zMDAxQzE0Ni43MTEgOTYuMzAwMSAxNDUuNDcgOTUuODgwNiAxNDQuNjI0IDk1LjA0MTVDMTQzLjc4NSA5NC4yMDI1IDE0My4zNjUgOTMuMDA4NSAxNDMuMzY1IDkxLjQ1OTVWOTAuMDI4OUMxNDMuMzY1IDg4LjQ3MjcgMTQzLjc4NSA4Ny4yNzUxIDE0NC42MjQgODYuNDM2MUMxNDUuNDcgODUuNTg5OSAxNDYuNzExIDg1LjE2NjggMTQ4LjM0NiA4NS4xNjY4QzE0OS45NzMgODUuMTY2OCAxNTEuMjA3IDg1LjU4OTkgMTUyLjA0NiA4Ni40MzYxQzE1Mi44ODUgODcuMjc1MSAxNTMuMzA1IDg4LjQ3MjcgMTUzLjMwNSA5MC4wMjg5VjkxLjQ1OTVDMTUzLjMwNSA5My4wMDg1IDE1Mi44ODUgOTQuMjAyNSAxNTIuMDQ2IDk1LjA0MTVDMTUxLjIxNCA5NS44ODA2IDE0OS45ODEgOTYuMzAwMSAxNDguMzQ2IDk2LjMwMDFaTTE0OC4zNDYgOTQuMDk1QzE0OS4wNjMgOTQuMDk1IDE0OS42MDggOTMuODc5OCAxNDkuOTgxIDkzLjQ0OTVDMTUwLjM2MSA5My4wMTkzIDE1MC41NTEgOTIuNDAyNSAxNTAuNTUxIDkxLjU5OTRWODkuODg5QzE1MC41NTEgODkuMDcxNSAxNTAuMzYxIDg4LjQ0NzYgMTQ5Ljk4MSA4OC4wMTc0QzE0OS42MDggODcuNTc5OSAxNDkuMDYzIDg3LjM2MTIgMTQ4LjM0NiA4Ny4zNjEyQzE0Ny42MjEgODcuMzYxMiAxNDcuMDY5IDg3LjU3OTkgMTQ2LjY4OSA4OC4wMTc0QzE0Ni4zMTYgODguNDQ3NiAxNDYuMTMgODkuMDcxNSAxNDYuMTMgODkuODg5VjkxLjU5OTRDMTQ2LjEzIDkyLjQwMjUgMTQ2LjMxNiA5My4wMTkzIDE0Ni42ODkgOTMuNDQ5NUMxNDcuMDY5IDkzLjg3OTggMTQ3LjYyMSA5NC4wOTUgMTQ4LjM0NiA5NC4wOTVaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTYzLjI2OCA5Ni4wMDk3Vjg5LjY3MzlDMTYzLjI2OCA4OS4yMjkzIDE2My4yMDcgODguODQ1NiAxNjMuMDg1IDg4LjUyMjlDMTYyLjk3IDg4LjIwMDIgMTYyLjc3NiA4Ny45NDkyIDE2Mi41MDQgODcuNzdDMTYyLjIzMSA4Ny41OTA3IDE2MS44NTkgODcuNTAxIDE2MS4zODUgODcuNTAxQzE2MC45NjkgODcuNTAxIDE2MC42MDQgODcuNTc2MyAxNjAuMjg4IDg3LjcyNjlDMTU5Ljk4IDg3Ljg3NzUgMTU5LjcyNSA4OC4wODE5IDE1OS41MjQgODguMzQwMUMxNTkuMzMxIDg4LjU5MTEgMTU5LjE4NCA4OC44Nzc5IDE1OS4wODMgODkuMjAwNkwxNTguNjUzIDg3LjY5NDdIMTU5LjE2OUMxNTkuMjg0IDg3LjIyODUgMTU5LjQ3NCA4Ni44MDkgMTU5LjczOSA4Ni40MzYxQzE2MC4wMTIgODYuMDYzMiAxNjAuMzc4IDg1Ljc2OTIgMTYwLjgzNyA4NS41NTQxQzE2MS4zMDMgODUuMzMxNyAxNjEuODg0IDg1LjIyMDYgMTYyLjU3OSA4NS4yMjA2QzE2My4zOSA4NS4yMjA2IDE2NC4wNDYgODUuMzc0OCAxNjQuNTQ4IDg1LjY4MzFDMTY1LjA1IDg1Ljk4NDMgMTY1LjQxOSA4Ni40MzYxIDE2NS42NTYgODcuMDM4NUMxNjUuODk5IDg3LjY0MDkgMTY2LjAyMSA4OC4zODY3IDE2Ni4wMjEgODkuMjc1OVY5Ni4wMDk3SDE2My4yNjhaTTE1Ni4zOTQgOTYuMDA5N1Y4NS40NTcySDE1OS4xNDhMMTU5LjA0IDg4LjAyODFMMTU5LjE0OCA4OC4yNTRWOTYuMDA5N0gxNTYuMzk0WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE3My40NDkgOTYuMjM1NkMxNzIuNTUyIDk2LjIzNTYgMTcxLjgzNSA5Ni4xMDI5IDE3MS4yOTcgOTUuODM3NkMxNzAuNzY3IDk1LjU2NSAxNzAuMzgzIDk1LjE1NjMgMTcwLjE0NiA5NC42MTEzQzE2OS45MSA5NC4wNjYzIDE2OS43OTEgOTMuMzk1OCAxNjkuNzkxIDkyLjU5OThWODYuNDQ2OUgxNzIuNTI0VjkyLjE5MUMxNzIuNTI0IDkyLjc2NDcgMTcyLjY1MyA5My4xODc4IDE3Mi45MTEgOTMuNDYwM0MxNzMuMTc2IDkzLjcyNTYgMTczLjYzOSA5My44NTgzIDE3NC4yOTggOTMuODU4M0MxNzQuNjg2IDkzLjg1ODMgMTc1LjA1OSA5My44MTg5IDE3NS40MTcgOTMuNzRDMTc1Ljc3NiA5My42NTM5IDE3Ni4xMDYgOTMuNTQyOCAxNzYuNDA3IDkzLjQwNjVMMTc2LjE3IDk1LjcwODVDMTc1LjgxMiA5NS44NzM0IDE3NS4zOTkgOTYuMDAyNSAxNzQuOTMzIDk2LjA5NTdDMTc0LjQ3NCA5Ni4xODg5IDE3My45NzkgOTYuMjM1NiAxNzMuNDQ5IDk2LjIzNTZaTTE2OC4yNjQgODcuNzE2MlY4NS41NDMzSDE3Ni4zMUwxNzYuMDczIDg3LjcxNjJIMTY4LjI2NFpNMTY5LjgyNCA4NS43NDc3TDE2OS44MTMgODIuOTUwOUwxNzIuNTU2IDgyLjY3MTJMMTcyLjQ0OCA4NS43NDc3SDE2OS44MjRaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTgzLjM2NiA5Ni4yNzg2QzE4MS43MzggOTYuMjc4NiAxODAuNTMgOTUuODQ4MyAxNzkuNzQxIDk0Ljk4NzhDMTc4Ljk1OSA5NC4xMjcyIDE3OC41NjkgOTIuOTE4OSAxNzguNTY5IDkxLjM2MjdWOTAuMDcxOUMxNzguNTY5IDg4LjUyMjkgMTc4Ljk2MyA4Ny4zMjE4IDE3OS43NTIgODYuNDY4NEMxODAuNTQxIDg1LjYxNSAxODEuNzQ1IDg1LjE4ODMgMTgzLjM2NiA4NS4xODgzQzE4My43ODkgODUuMTg4MyAxODQuMTg0IDg1LjIyNDIgMTg0LjU0OSA4NS4yOTU5QzE4NC45MjIgODUuMzYwNCAxODUuMjU5IDg1LjQ1MDEgMTg1LjU2IDg1LjU2NDhDMTg1Ljg2OSA4NS42Nzk1IDE4Ni4xMzggODUuODAxNSAxODYuMzY3IDg1LjkzMDVMMTg2LjU5MyA4OC4yNDMzQzE4Ni4yNDIgODguMDIwOSAxODUuODQ3IDg3LjgzNDUgMTg1LjQxIDg3LjY4MzlDMTg0Ljk4IDg3LjUzMzMgMTg0LjQ4MSA4Ny40NTggMTgzLjkxNSA4Ny40NThDMTgzLjAyNSA4Ny40NTggMTgyLjM3MyA4Ny42ODc1IDE4MS45NTcgODguMTQ2NEMxODEuNTQ4IDg4LjU5ODIgMTgxLjM0NCA4OS4yNTggMTgxLjM0NCA5MC4xMjU3VjkxLjI2NTlDMTgxLjM0NCA5Mi4xMjY1IDE4MS41NTUgOTIuNzg5OCAxODEuOTc4IDkzLjI1NTlDMTgyLjQwOSA5My43MjIgMTgzLjA2OCA5My45NTUxIDE4My45NTggOTMuOTU1MUMxODQuNTI0IDkzLjk1NTEgMTg1LjAyNiA5My44ODM0IDE4NS40NjQgOTMuNzRDMTg1LjkwMSA5My41ODk0IDE4Ni4zMSA5My40MDY1IDE4Ni42OSA5My4xOTE0TDE4Ni40NjQgOTUuNTE0OEMxODYuMTEzIDk1LjcxNTYgMTg1LjY3MiA5NS44OTEzIDE4NS4xNDEgOTYuMDQxOUMxODQuNjEgOTYuMTk5NyAxODQuMDE5IDk2LjI3ODYgMTgzLjM2NiA5Ni4yNzg2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE4OS41ODEgOTYuMDA5N1Y4MS43ODkySDE5Mi4zNDVWOTYuMDA5N0gxODkuNTgxWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE5NS44ODggOTYuMDA5N1Y4NS40NTcySDE5OC42NDJWOTYuMDA5N0gxOTUuODg4Wk0xOTcuMjY1IDg0LjIwOTVDMTk2Ljc0MSA4NC4yMDk1IDE5Ni4zNTQgODQuMDg3NSAxOTYuMTAzIDgzLjg0MzdDMTk1Ljg1OSA4My41OTI3IDE5NS43MzcgODMuMjQ4NSAxOTUuNzM3IDgyLjgxMTFWODIuNzU3M0MxOTUuNzM3IDgyLjMxOTggMTk1Ljg1OSA4MS45NzU2IDE5Ni4xMDMgODEuNzI0NkMxOTYuMzU0IDgxLjQ3MzYgMTk2Ljc0MSA4MS4zNDgxIDE5Ny4yNjUgODEuMzQ4MUMxOTcuNzgxIDgxLjM0ODEgMTk4LjE2NSA4MS40NzM2IDE5OC40MTYgODEuNzI0NkMxOTguNjY3IDgxLjk3NTYgMTk4Ljc5MiA4Mi4zMTk4IDE5OC43OTIgODIuNzU3M1Y4Mi44MTExQzE5OC43OTIgODMuMjU1NyAxOTguNjY3IDgzLjU5OTkgMTk4LjQxNiA4My44NDM3QzE5OC4xNjUgODQuMDg3NSAxOTcuNzgxIDg0LjIwOTUgMTk3LjI2NSA4NC4yMDk1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTIwNS44NDMgODEuNDU1N0MyMDYuNDAyIDgxLjQ1NTcgMjA2LjkxOCA4MS41MDIzIDIwNy4zOTIgODEuNTk1NkMyMDcuODY1IDgxLjY4ODggMjA4LjI4NCA4MS44MDM1IDIwOC42NSA4MS45Mzk4TDIwOC44OTcgODQuMDA1MUMyMDguNTg5IDgzLjkxMTggMjA4LjI2NiA4My44MzY1IDIwNy45MjkgODMuNzc5MkMyMDcuNTk5IDgzLjcxNDYgMjA3LjIzNCA4My42ODI0IDIwNi44MzIgODMuNjgyNEMyMDYuMzUyIDgzLjY4MjQgMjA1Ljk3MiA4My43MzYyIDIwNS42OTIgODMuODQzN0MyMDUuNDE5IDgzLjk1MTMgMjA1LjIyNiA4NC4xMDU1IDIwNS4xMTEgODQuMzA2M0MyMDQuOTk2IDg0LjUwNzEgMjA0LjkzOSA4NC43NDM3IDIwNC45MzkgODUuMDE2MlY4NS4wNDg1QzIwNC45MzkgODUuMjQyMSAyMDQuOTY4IDg1LjQyNSAyMDUuMDI1IDg1LjU5NzFDMjA1LjA4MiA4NS43NjkyIDIwNS4xNTEgODUuOTIzNCAyMDUuMjI5IDg2LjA1OTZMMjAzLjQzMyA4Ni4xMjQyVjg1LjgzMzdDMjAzLjEwMyA4NS42NzYgMjAyLjgyMyA4NS40NDI5IDIwMi41OTQgODUuMTM0NUMyMDIuMzY1IDg0LjgyNjIgMjAyLjI1IDg0LjQ0MjUgMjAyLjI1IDgzLjk4MzZWODMuOTI5OEMyMDIuMjUgODMuMTY5NiAyMDIuNTQgODIuNTY3MiAyMDMuMTIxIDgyLjEyMjZDMjAzLjcwOSA4MS42NzggMjA0LjYxNiA4MS40NTU3IDIwNS44NDMgODEuNDU1N1pNMjAyLjYxNiA5Ni4wMDk3Vjg2LjY5NDNIMjA1LjM1OFY5Ni4wMDk3SDIwMi42MTZaTTIwMS4wODggODguMDkyN1Y4NS45MDlMMjAzLjc1NiA4NS45MzA1TDIwNC44MzEgODUuOTA5SDIwOC44NzZMMjA4LjYzOSA4OC4wOTI3SDIwMS4wODhaTTIxMi43NDggODEuNDU1N0MyMTMuMzA4IDgxLjQ1NTcgMjEzLjgyNCA4MS41MDIzIDIxNC4yOTcgODEuNTk1NkMyMTQuNzcxIDgxLjY4ODggMjE1LjE5IDgxLjgwMzUgMjE1LjU1NiA4MS45Mzk4TDIxNS44MDMgODQuMDA1MUMyMTUuNDk1IDgzLjkxMTggMjE1LjE3MiA4My44MzY1IDIxNC44MzUgODMuNzc5MkMyMTQuNTA1IDgzLjcxNDYgMjE0LjE0IDgzLjY4MjQgMjEzLjczOCA4My42ODI0QzIxMy4yNTggODMuNjgyNCAyMTIuODc3IDgzLjczNjIgMjEyLjU5OCA4My44NDM3QzIxMi4zMjUgODMuOTUxMyAyMTIuMTMyIDg0LjEwNTUgMjEyLjAxNyA4NC4zMDYzQzIxMS45MDIgODQuNTA3MSAyMTEuODQ1IDg0Ljc0MzcgMjExLjg0NSA4NS4wMTYyVjg1LjA0ODVDMjExLjg0NSA4NS4yNDIxIDIxMS44NzQgODUuNDI1IDIxMS45MzEgODUuNTk3MUMyMTEuOTg4IDg1Ljc2OTIgMjEyLjA1NiA4NS45MjM0IDIxMi4xMzUgODYuMDU5NkwyMTAuMzM5IDg2LjEyNDJWODUuODMzN0MyMTAuMDA5IDg1LjY3NiAyMDkuNzI5IDg1LjQ0MjkgMjA5LjUgODUuMTM0NUMyMDkuMjcgODQuODI2MiAyMDkuMTU2IDg0LjQ0MjUgMjA5LjE1NiA4My45ODM2VjgzLjkyOThDMjA5LjE1NiA4My4xNjk2IDIwOS40NDYgODIuNTY3MiAyMTAuMDI3IDgyLjEyMjZDMjEwLjYxNSA4MS42NzggMjExLjUyMiA4MS40NTU3IDIxMi43NDggODEuNDU1N1pNMjA5LjUyMSA5Ni4wMDk3Vjg2LjY5NDNIMjEyLjI2NFY5Ni4wMDk3SDIwOS41MjFaTTIwNy45OTQgODguMDkyN1Y4NS45MDlMMjEwLjY2MiA4NS45MzA1TDIxMS43MzcgODUuOTA5SDIxNS43ODJMMjE1LjU0NSA4OC4wOTI3SDIwNy45OTRaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTQ2LjY3NCAxMTkuNzQ0VjExNi4yNDZIMTUwLjM5OUMxNTEuNDY3IDExNi4yNDYgMTUyLjI2OCAxMTUuOTU3IDE1Mi44MDIgMTE1LjM3OEMxNTMuMzM2IDExNC44IDE1My42MDMgMTEzLjk3MiAxNTMuNjAzIDExMi44OTVWMTA5LjQ3N0MxNTMuNjAzIDEwOC40IDE1My4zMzYgMTA3LjU3NyAxNTIuODAyIDEwNy4wMDhDMTUyLjI2OCAxMDYuNDI5IDE1MS40NjcgMTA2LjE0IDE1MC4zOTkgMTA2LjE0SDE0Ni42NjFWMTAyLjY4MkgxNTAuNTQ2QzE1Mi45NzYgMTAyLjY4MiAxNTQuNzkxIDEwMy4yNyAxNTUuOTkzIDEwNC40NDRDMTU3LjIwMyAxMDUuNjEgMTU3LjgwOSAxMDcuMjg4IDE1Ny44MDkgMTA5LjQ3N1YxMTIuOTA4QzE1Ny44MDkgMTE1LjEwNyAxNTcuMjA4IDExNi43OTggMTU2LjAwNiAxMTcuOTgxQzE1NC44MDUgMTE5LjE1NiAxNTIuOTg1IDExOS43NDQgMTUwLjU0NiAxMTkuNzQ0SDE0Ni42NzRaTTE0My43NTEgMTE5Ljc0NFYxMDIuNjgySDE0Ny44NjNWMTE5Ljc0NEgxNDMuNzUxWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE2MS40MyAxMTkuNzQ0TDE2MS45NjQgMTAyLjY4MkgxNjcuNzg1TDE3MC42MDIgMTEyLjg1NUgxNzAuNzc1TDE3My41OTIgMTAyLjY4MkgxNzkuNDEzTDE3OS45NDcgMTE5Ljc0NEgxNzUuOTAyTDE3NS43ODIgMTE0LjE1TDE3NS42NjIgMTA3LjM0MUgxNzUuNDYxTDE3Mi41MzggMTE3Ljc0MUgxNjguODI2TDE2NS45MDMgMTA3LjM0MUgxNjUuNjg5TDE2NS41ODIgMTE0LjE2M0wxNjUuNDc1IDExOS43NDRIMTYxLjQzWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE4Ny4zMjEgMTE5Ljc0NEwxODIuNjQ5IDEwMi42ODJIMTg2Ljk3NEwxOTAuMzI1IDExNi42MzNIMTkwLjYzMkwxOTMuOTk2IDEwMi42ODJIMTk4LjMwOEwxOTMuNjQ5IDExOS43NDRIMTg3LjMyMVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xOTguNzg0IC0xMS43MzYzTDE5NS42MTEgLTguNzA2MThMMTkxLjMxMSAtOS42MDcwM0wxOTMuMjE1IC01LjYzNTA4TDE5MS4wNDUgLTEuODI2OTJMMTk1LjM4NiAtMi40MjA2NkwxOTguMzU0IDAuODM0NzAxTDE5OS4xMzIgLTMuNDg1MzFMMjAzLjE0NSAtNS4yODcwMkwxOTkuMjc2IC03LjM3NTM3TDE5OC43ODQgLTExLjczNjNaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjIzLjcwMSAyLjc2MTMzTDIxOS42NjggNC41NDI1NkwyMTUuOTQxIDIuMjI5TDIxNi4zNzEgNi41ODk5NkwyMTMuMDE0IDkuNDM1ODVMMjE3LjMxMyAxMC4zNzc3TDIxOC45NzIgMTQuNDMxNUwyMjEuMTgzIDEwLjY0MzhMMjI1LjU2NCAxMC4zMTYyTDIyMi42NTcgNy4wNDAzOUwyMjMuNzAxIDIuNzYxMzNaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjQyLjEyNyAyNC45MTQyTDIzNy43NDYgMjUuMjAwOUwyMzUuMDIzIDIxLjc2MTJMMjMzLjkzOCAyNi4wMTk4TDIyOS44MjIgMjcuNTM0OUwyMzMuNTI4IDI5Ljg2ODlMMjMzLjcxMiAzNC4yNzA4TDIzNy4wOTEgMzEuNDY1OUwyNDEuMzA4IDMyLjY1MzRMMjM5LjY5MSAyOC41NzkxTDI0Mi4xMjcgMjQuOTE0MloiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yNTEuODcyIDUyLjAzODlMMjQ3LjY3NSA1MC44MTA1TDI0Ni4yODMgNDYuNjMzOEwyNDMuODA2IDUwLjI1NzdMMjM5LjQyNCA1MC4yNzgyTDI0Mi4xMDYgNTMuNzU4N0wyNDAuNzc2IDU3LjkzNTRMMjQ0LjkxMSA1Ni40NjEzTDI0OC40NzQgNTkuMDIwNkwyNDguMzUxIDU0LjYzOTFMMjUxLjg3MiA1Mi4wMzg5WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTI1MS43NzEgODAuODQ1OUwyNDguMjI5IDc4LjI0NTdMMjQ4LjM3MyA3My44NjQzTDI0NC43OSA3Ni40MjM1TDI0MC42NTQgNzQuOTQ5NEwyNDIuMDA1IDc5LjEyNjFMMjM5LjMwMyA4Mi42MDY3TDI0My43MDUgODIuNjI3MUwyNDYuMTgyIDg2LjI1MUwyNDcuNTU0IDgyLjA3NDNMMjUxLjc3MSA4MC44NDU5WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTI0MS44MDEgMTA3Ljg5NUwyMzkuMzY1IDEwNC4yNTFMMjQxLjAwMiAxMDAuMTU2TDIzNi43NjQgMTAxLjM2NEwyMzMuMzg2IDk4LjUzODZMMjMzLjIyMiAxMDIuOTRMMjI5LjQ5NiAxMDUuMjc1TDIzMy42MzIgMTA2LjgxTDIzNC42OTYgMTExLjA2OUwyMzcuNDIgMTA3LjYwOUwyNDEuODAxIDEwNy44OTVaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjIzLjIxMSAxMjkuOTA0TDIyMi4xNjYgMTI1LjY0NUwyMjUuMDc0IDEyMi4zNDlMMjIwLjY5MiAxMjIuMDIxTDIxOC40ODEgMTE4LjIzM0wyMTYuODIzIDEyMi4zMDhMMjEyLjUyMyAxMjMuMjI5TDIxNS44ODEgMTI2LjA3NUwyMTUuNDUxIDEzMC40NTZMMjE5LjE3NyAxMjguMTQzTDIyMy4yMTEgMTI5LjkwNFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xOTguMTkxIDE0NC4yMTRMMTk4LjY2MSAxMzkuODUzTDIwMi41MzEgMTM3Ljc2NUwxOTguNTM5IDEzNS45NjNMMTk3Ljc0IDEzMS42NDNMMTk0Ljc5MiAxMzQuODk4TDE5MC40MzEgMTM0LjMwNUwxOTIuNjIyIDEzOC4xMTNMMTkwLjcxOCAxNDIuMDY0TDE5NS4wMTcgMTQxLjE4NEwxOTguMTkxIDE0NC4yMTRaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTY5Ljc5NCAxNDkuMTA2TDE3MS43MzkgMTQ1LjE3NUwxNzYuMDggMTQ0LjU0MUwxNzIuOTI3IDE0MS40NjlMMTczLjY4NCAxMzcuMTQ5TDE2OS43OTQgMTM5LjE5N0wxNjUuOTA0IDEzNy4xNDlMMTY2LjY0MSAxNDEuNDY5TDE2My40ODggMTQ0LjU0MUwxNjcuODQ5IDE0NS4xNzVMMTY5Ljc5NCAxNDkuMTA2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE0MS40MTggMTQzLjk4OUwxNDQuNTkxIDE0MC45NThMMTQ4Ljg5MSAxNDEuODU5TDE0Ni45ODcgMTM3Ljg4N0wxNDkuMTc3IDEzNC4wNzlMMTQ0LjgxNiAxMzQuNjczTDE0MS44NjggMTMxLjQxN0wxNDEuMDcgMTM1LjczN0wxMzcuMDc3IDEzNy41NkwxNDAuOTQ3IDEzOS42MjhMMTQxLjQxOCAxNDMuOTg5WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTExNi41MiAxMjkuNDk1TDEyMC41MzMgMTI3LjczNEwxMjQuMjc5IDEzMC4wMjdMMTIzLjgyOSAxMjUuNjY2TDEyNy4xODcgMTIyLjgyTDEyMi44ODcgMTIxLjg5OUwxMjEuMjI5IDExNy44MjVMMTE5LjAxOCAxMjEuNjEyTDExNC42MzYgMTIxLjk0TDExNy41NjQgMTI1LjIxNkwxMTYuNTIgMTI5LjQ5NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik05OC4wNzQgMTA3LjM0TDEwMi40NTUgMTA3LjA1NEwxMDUuMTc4IDExMC41MTRMMTA2LjI2NCAxMDYuMjU1TDExMC4zNzkgMTA0LjcyTDEwNi42NzMgMTAyLjM4NkwxMDYuNTA5IDk3Ljk4MzlMMTAzLjEzMSAxMDAuNzg5TDk4Ljg5MjkgOTkuNjAxM0wxMDAuNTEgMTAzLjY5Nkw5OC4wNzQgMTA3LjM0WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTg4LjMyNzYgODAuMjEyMUw5Mi41NDUzIDgxLjQ2MUw5My45MTcgODUuNjE3Mkw5Ni4zOTQ0IDgxLjk5MzNMMTAwLjc5NiA4MS45NzI5TDk4LjA5MzcgNzguNDkyM0w5OS40NDUgNzQuMzE1Nkw5NS4zMDkzIDc1Ljc4OTdMOTEuNzI2MyA3My4yMzA1TDkxLjg2OTYgNzcuNjMyNEw4OC4zMjc2IDgwLjIxMjFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNODguNDMwNyA1MS40MDU2TDkxLjk3MjcgNTQuMDA1OEw5MS44NDk4IDU4LjM4NzJMOTUuNDEyMyA1NS44MjhMOTkuNTQ4IDU3LjMwMjFMOTguMjE3MiA1My4xMjU0TDEwMC44OTkgNDkuNjQ0OUw5Ni40OTc0IDQ5LjYyNDRMOTQuMDQwNSA0Ni4wMDA1TDkyLjY0ODMgNTAuMTc3Mkw4OC40MzA3IDUxLjQwNTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOTguMzk5OSAyNC4zNkwxMDAuODM2IDI4LjAwNDRMOTkuMjE4OSAzMi4wOTkyTDEwMy40MzcgMzAuOTExN0wxMDYuODE1IDMzLjcxNjZMMTA2Ljk5OSAyOS4zMTQ3TDExMC43MDUgMjYuOTgwN0wxMDYuNTg5IDI1LjQ0NTFMMTA1LjUwNCAyMS4xODY1TDEwMi43ODEgMjQuNjQ2Nkw5OC4zOTk5IDI0LjM2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTExNy4wMTIgMi4zNTE2NkwxMTguMDU2IDYuNjMwNzJMMTE1LjEyOCA5LjkwNjU2TDExOS41MSAxMC4yMzQxTDEyMS43MjEgMTQuMDIxOEwxMjMuNCA5Ljk0NzUxTDEyNy42NzkgOS4wMjYxOEwxMjQuMzIxIDYuMTgwM0wxMjQuNzcyIDEuODE5MzRMMTIxLjAyNSA0LjExMjQyTDExNy4wMTIgMi4zNTE2NloiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNDIuMDMgLTExLjk2MTRMMTQxLjUzOSAtNy42MDA0N0wxMzcuNjY5IC01LjUxMjEyTDE0MS42ODIgLTMuNzEwNDFMMTQyLjQ2IDAuNjA5NjAzTDE0NS40MjkgLTIuNjQ1NzZMMTQ5Ljc3IC0yLjA1MjAxTDE0Ny41OTkgLTUuODYwMThMMTQ5LjUwMyAtOS44MTE2NkwxNDUuMjA0IC04LjkzMTI4TDE0Mi4wMyAtMTEuOTYxNFoiIGZpbGw9IndoaXRlIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMTIxM181NTIwIj4KPHJlY3Qgd2lkdGg9IjI0NSIgaGVpZ2h0PSIxNDguNzUiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDAuMzU2NDQ1KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgpqaXNzdWVySWNvbqJmZm9ybWF0Y3N2Z2RkYXRhWTGYPHN2ZyB3aWR0aD0iMzMiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMyAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE2LjY2NjUgMzJDMjUuNTAzMSAzMiAzMi42NjY1IDI0LjgzNjYgMzIuNjY2NSAxNkMzMi42NjY1IDcuMTYzNDQgMjUuNTAzMSAwIDE2LjY2NjUgMEM3LjgyOTk1IDAgMC42NjY1MDQgNy4xNjM0NCAwLjY2NjUwNCAxNkMwLjY2NjUwNCAyNC44MzY2IDcuODI5OTUgMzIgMTYuNjY2NSAzMloiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTE2LjYxNzMgMzAuMjY0NEMyNC40ODY4IDMwLjI2NDQgMzAuODY2MiAyMy44ODUgMzAuODY2MiAxNi4wMTU1QzMwLjg2NjIgOC4xNDYwNSAyNC40ODY4IDEuNzY2NiAxNi42MTczIDEuNzY2NkM4Ljc0Nzg2IDEuNzY2NiAyLjM2ODQxIDguMTQ2MDUgMi4zNjg0MSAxNi4wMTU1QzIuMzY4NDEgMjMuODg1IDguNzQ3ODYgMzAuMjY0NCAxNi42MTczIDMwLjI2NDRaIiBmaWxsPSIjMDAzNDU5Ii8+CjxwYXRoIGQ9Ik0xNi42MTczIDEwLjQwNzdMMjUuMzMyMyAxMC44OTQ1TDE2LjYxNzMgMTEuMzgxNEw3LjkwMjM0IDEwLjg5NDVMMTYuNjE3MyAxMC40MDc3WiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMTYuNjE3MyAyMS40OTA3TDcuOTAyMjcgMjEuMDAzOUwxNi42MTczIDIwLjUxNzFMMjUuMzMyMyAyMS4wMDM5TDE2LjYxNzMgMjEuNDkwN1oiIGZpbGw9IiMwQTRCNzciLz4KPHBhdGggZD0iTTE2LjYxNzMgOC44MzkzNkwyMy42MDQ1IDkuMzI2MThMMTYuNjE3MyA5LjgxMzAxTDkuNjMwMTMgOS4zMjYxOEwxNi42MTczIDguODM5MzZaIiBmaWxsPSIjMEE0Qjc3Ii8+CjxwYXRoIGQ9Ik0xNi42MTczIDIzLjA1OTFMOS42MzAxNiAyMi41NzIzTDE2LjYxNzMgMjIuMDg1NEwyMy42MDQ1IDIyLjU3MjNMMTYuNjE3MyAyMy4wNTkxWiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMTYuNjE3MyA3LjI3MUwyMS42MzcyIDcuNzU3ODJMMTYuNjE3MyA4LjI0NDY1TDExLjU5NzQgNy43NTc4MkwxNi42MTczIDcuMjcxWiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMTYuNjE3MyAyNC42MjdMMTEuNTk3NCAyNC4xNDAxTDE2LjYxNzMgMjMuNjUzM0wyMS42MzcyIDI0LjE0MDFMMTYuNjE3MyAyNC42MjdaIiBmaWxsPSIjMEE0Qjc3Ii8+CjxwYXRoIGQ9Ik0zMC4yNDMyIDE1Ljc4MkMzMC4yNDMyIDIzLjMwNTMgMjQuMTQxMSAyOS40MDM3IDE2LjYxNzggMjkuNDAzN0M5LjA5NDU2IDI5LjQwMzcgMi45OTYwOSAyMy4zMDUzIDIuOTk2MDkgMTUuNzgyQzIuOTk2MDkgMTUuMjYxOSAzLjAyNTQgMTQuNzQ5MSAzLjA4NCAxNC4yNDM3QzMuODQ5NTEgMjEuMDQxNyA5LjYxNDY3IDI2LjMyMzQgMTYuNjE3OCAyNi4zMjM0QzIzLjYyMSAyNi4zMjM0IDI5LjM4OTggMjEuMDQxNyAzMC4xNTUzIDE0LjI0MzdDMzAuMjEzOSAxNC43NDkxIDMwLjI0MzIgMTUuMjYxOSAzMC4yNDMyIDE1Ljc4MloiIGZpbGw9IiMwMDJENEQiLz4KPHBhdGggZD0iTTE0LjE4NjkgMTYuMTQxMkwxMS44Mjg5IDE0LjIzODRDMTEuNzY0MSAxNC4xODYzIDExLjcyNjMgMTQuMTA3NiAxMS43MjYzIDE0LjAyNDNMMTEuNzI2MyAxMi41NDkyQzExLjcyNjMgMTIuNDQyMiAxMS43ODg3IDEyLjM0NDYgMTEuODg1OSAxMi4yOTk1TDEzLjg1OTYgMTEuMzg0MUMxNC4wMDI0IDExLjMxODEgMTQuMDYwNiAxMS4xNDU5IDEzLjk4NzYgMTEuMDA2NUwxMy4zMzIzIDkuNzU4NTRDMTMuMzExMyA5LjcxOTE3IDEzLjMwMDcgOS42NzUyOSAxMy4zMDA3IDkuNjMwNTlMMTMuMzAwNyA4Ljk3NjA5QzEzLjMwMDcgOC44Nzg4OSAxMy4zNTI0IDguNzg4NjcgMTMuNDM2IDguNzM5MDVMMTcuNDY5NyA2LjM1ODQ3QzE3LjU2ODUgNi4zMDAyMyAxNy42OTMyIDYuMzA5MjYgMTcuNzgyNiA2LjM4MTQzTDIxLjYwNjcgOS40NzAyNUwyMS42MDY3IDExLjU4ODhDMjEuNjA2NyAxMS43NDA5IDIxLjQ4MzcgMTEuODY0IDIxLjMzMTUgMTEuODY0TDE5LjE1ODEgMTEuOTk5MkMxOS4wMDU5IDExLjk5OTIgMTguODgyOSAxMi4xMjIyIDE4Ljg4MjkgMTIuMjc0M0wxOC43MDM2IDE0LjMxODhDMTguNzAzNiAxNC40NzA5IDE4LjgyNjcgMTQuNTkzOSAxOC45Nzg4IDE0LjU5MzlDMTkuMTMxIDE0LjU5MzkgMTkuMjU0IDE0LjcxNyAxOS4yNTQgMTQuODY5MUwxOS4yNTQgMTYuNzMyNkMxOS4yNTQgMTYuODg0NyAxOS4xMzEgMTcuMDA3NyAxOC45Nzg4IDE3LjAwNzdMMTcuMTE0MSAxNy4wMDc3QzE3LjA4NzUgMTcuMDA3NyAxNy4wNjA4IDE3LjAwMzYgMTcuMDM1IDE2Ljk5NjNMMTQuMTg2NSAxNi4xNDEyIiBmaWxsPSIjMDBBOEU4Ii8+CjxwYXRoIGQ9Ik0yMC4yOTk2IDE0Ljc5MjNMMjAuOTAwMyAxNC4xMTVDMjAuOTI3OSAxNC4wODM4IDIwLjk0MDMgMTQuMDQyIDIwLjkzNDEgMTQuMDAwOEwyMC43NDkyIDEyLjc3MUMyMC43NDA5IDEyLjcxNTcgMjAuNzAwNiAxMi42NzA2IDIwLjY0NjYgMTIuNjU2MkwyMC4wNDQgMTIuNDk0N0MxOS45NjkgMTIuNDc0NiAxOS44OTIgMTIuNTE5MSAxOS44NzE5IDEyLjU5NDFMMTkuNDc1OCAxNC4wNzI1QzE5LjQ1NTcgMTQuMTQ3NSAxOS41MDAyIDE0LjIyNDUgMTkuNTc1MSAxNC4yNDQ2TDE5Ljg5NjQgMTQuMzMwN0MxOS45NDc4IDE0LjM0NDUgMTkuOTg3IDE0LjM4NjEgMTkuOTk3NyAxNC40MzgyTDIwLjA1NjggMTQuNzI3MkMyMC4wOCAxNC44NDA3IDIwLjIyMjggMTQuODc4OSAyMC4yOTk2IDE0Ljc5MjNaIiBmaWxsPSIjMDBBOEU4Ii8+CjxwYXRoIGQ9Ik05LjQ5MDQxIDIxLjAwNzhMOS41NjM0NCAxOC43ODU1SDEwLjIyMDhMMTAuNjM4MSAyMC4xOTkySDEwLjY2MDdMMTEuMDc2MyAxOC43ODU1SDExLjczMTlMMTEuODA2NyAyMS4wMDc4SDExLjM2NUwxMS4zNDQxIDIwLjIwNzlMMTEuMzI1IDE5LjI4NDVIMTEuMjk3MkwxMC44NjA3IDIwLjc0ODdIMTAuNDM2NEw5Ljk5ODE4IDE5LjI4NDVIOS45NzAzNUw5Ljk1MTIzIDIwLjIwOTZMOS45MzIxIDIxLjAwNzhIOS40OTA0MVpNMTMuMTAwMSAyMS4wNTQ4QzEyLjgzNTggMjEuMDU0OCAxMi42MzUzIDIwLjk4NjkgMTIuNDk4NSAyMC44NTEzQzEyLjM2MjggMjAuNzE1NyAxMi4yOTUgMjAuNTIyNyAxMi4yOTUgMjAuMjcyMlYyMC4wNDFDMTIuMjk1IDE5Ljc4OTQgMTIuMzYyOCAxOS41OTU4IDEyLjQ5ODUgMTkuNDYwMkMxMi42MzUzIDE5LjMyMzQgMTIuODM1OCAxOS4yNTUgMTMuMTAwMSAxOS4yNTVDMTMuMzYzMyAxOS4yNTUgMTMuNTYyNyAxOS4zMjM0IDEzLjY5ODMgMTkuNDYwMkMxMy44MzQgMTkuNTk1OCAxMy45MDE4IDE5Ljc4OTQgMTMuOTAxOCAyMC4wNDFWMjAuMjcyMkMxMy45MDE4IDIwLjUyMjcgMTMuODM0IDIwLjcxNTcgMTMuNjk4MyAyMC44NTEzQzEzLjU2MzggMjAuOTg2OSAxMy4zNjQ0IDIxLjA1NDggMTMuMTAwMSAyMS4wNTQ4Wk0xMy4xMDAxIDIwLjY5ODNDMTMuMjE2MSAyMC42OTgzIDEzLjMwNDIgMjAuNjYzNSAxMy4zNjQ0IDIwLjU5MzlDMTMuNDI1OSAyMC41MjQ0IDEzLjQ1NjYgMjAuNDI0NyAxMy40NTY2IDIwLjI5NDlWMjAuMDE4NEMxMy40NTY2IDE5Ljg4NjIgMTMuNDI1OSAxOS43ODUzIDEzLjM2NDQgMTkuNzE1OEMxMy4zMDQyIDE5LjY0NTEgMTMuMjE2MSAxOS42MDk3IDEzLjEwMDEgMTkuNjA5N0MxMi45ODMgMTkuNjA5NyAxMi44OTM4IDE5LjY0NTEgMTIuODMyMyAxOS43MTU4QzEyLjc3MjEgMTkuNzg1MyAxMi43NDE5IDE5Ljg4NjIgMTIuNzQxOSAyMC4wMTg0VjIwLjI5NDlDMTIuNzQxOSAyMC40MjQ3IDEyLjc3MjEgMjAuNTI0NCAxMi44MzIzIDIwLjU5MzlDMTIuODkzOCAyMC42NjM1IDEyLjk4MyAyMC42OTgzIDEzLjEwMDEgMjAuNjk4M1pNMTUuNTEyNCAyMS4wMDc4VjE5Ljk4MzZDMTUuNTEyNCAxOS45MTE3IDE1LjUwMjYgMTkuODQ5NyAxNS40ODI4IDE5Ljc5NzVDMTUuNDY0MyAxOS43NDUzIDE1LjQzMyAxOS43MDQ4IDE1LjM4ODkgMTkuNjc1OEMxNS4zNDQ5IDE5LjY0NjggMTUuMjg0NiAxOS42MzIzIDE1LjIwODEgMTkuNjMyM0MxNS4xNDA5IDE5LjYzMjMgMTUuMDgxNyAxOS42NDQ1IDE1LjAzMDcgMTkuNjY4OEMxNC45ODA5IDE5LjY5MzIgMTQuOTM5NyAxOS43MjYyIDE0LjkwNzMgMTkuNzY4QzE0Ljg3NiAxOS44MDg1IDE0Ljg1MjIgMTkuODU0OSAxNC44MzYgMTkuOTA3MUwxNC43NjY0IDE5LjY2MzZIMTQuODQ5OUMxNC44Njg0IDE5LjU4ODMgMTQuODk5MSAxOS41MjA0IDE0Ljk0MiAxOS40NjAyQzE0Ljk4NjEgMTkuMzk5OSAxNS4wNDUyIDE5LjM1MjQgMTUuMTE5NCAxOS4zMTc2QzE1LjE5NDggMTkuMjgxNiAxNS4yODg3IDE5LjI2MzcgMTUuNDAxMSAxOS4yNjM3QzE1LjUzMjEgMTkuMjYzNyAxNS42MzgyIDE5LjI4ODYgMTUuNzE5MyAxOS4zMzg0QzE1LjgwMDUgMTkuMzg3MSAxNS44NjAyIDE5LjQ2MDIgMTUuODk4NSAxOS41NTc1QzE1LjkzNzkgMTkuNjU0OSAxNS45NTc2IDE5Ljc3NTUgMTUuOTU3NiAxOS45MTkyVjIxLjAwNzhIMTUuNTEyNFpNMTQuNDAxMiAyMS4wMDc4VjE5LjMwMTlIMTQuODQ2NEwxNC44MjkgMTkuNzE3NUwxNC44NDY0IDE5Ljc1NFYyMS4wMDc4SDE0LjQwMTJaTTE3LjE1ODMgMjEuMDQ0M0MxNy4wMTMzIDIxLjA0NDMgMTYuODk3NCAyMS4wMjI5IDE2LjgxMDUgMjAuOThDMTYuNzI0NyAyMC45MzU5IDE2LjY2MjcgMjAuODY5OSAxNi42MjQ0IDIwLjc4MThDMTYuNTg2MSAyMC42OTM2IDE2LjU2NyAyMC41ODUzIDE2LjU2NyAyMC40NTY2VjE5LjQ2MTlIMTcuMDA4N1YyMC4zOTA1QzE3LjAwODcgMjAuNDgzMiAxNy4wMjk2IDIwLjU1MTYgMTcuMDcxMyAyMC41OTU3QzE3LjExNDIgMjAuNjM4NiAxNy4xODkgMjAuNjYgMTcuMjk1NiAyMC42NkMxNy4zNTgyIDIwLjY2IDE3LjQxODUgMjAuNjUzNyAxNy40NzY1IDIwLjY0MDlDMTcuNTM0NCAyMC42MjcgMTcuNTg3OCAyMC42MDkgMTcuNjM2NSAyMC41ODdMMTcuNTk4MiAyMC45NTkxQzE3LjU0MDIgMjAuOTg1OCAxNy40NzM2IDIxLjAwNjcgMTcuMzk4MiAyMS4wMjE3QzE3LjMyNCAyMS4wMzY4IDE3LjI0NCAyMS4wNDQzIDE3LjE1ODMgMjEuMDQ0M1pNMTYuMzIwMSAxOS42NjcxVjE5LjMxNThIMTcuNjIwOEwxNy41ODI2IDE5LjY2NzFIMTYuMzIwMVpNMTYuNTcyMiAxOS4zNDg5TDE2LjU3MDUgMTguODk2OEwxNy4wMTM5IDE4Ljg1MTVMMTYuOTk2NSAxOS4zNDg5SDE2LjU3MjJaTTE4Ljc2MTUgMjEuMDUxM0MxOC40OTgzIDIxLjA1MTMgMTguMzAzIDIwLjk4MTcgMTguMTc1NSAyMC44NDI2QzE4LjA0OTEgMjAuNzAzNSAxNy45ODU5IDIwLjUwODIgMTcuOTg1OSAyMC4yNTY2VjIwLjA0NzlDMTcuOTg1OSAxOS43OTc1IDE4LjA0OTcgMTkuNjAzMyAxOC4xNzcyIDE5LjQ2NTRDMTguMzA0NyAxOS4zMjc0IDE4LjQ5OTUgMTkuMjU4NCAxOC43NjE1IDE5LjI1ODRDMTguODI5OSAxOS4yNTg0IDE4Ljg5MzcgMTkuMjY0MiAxOC45NTI4IDE5LjI3NThDMTkuMDEzMSAxOS4yODYzIDE5LjA2NzUgMTkuMzAwOCAxOS4xMTYyIDE5LjMxOTNDMTkuMTY2MSAxOS4zMzc5IDE5LjIwOTYgMTkuMzU3NiAxOS4yNDY3IDE5LjM3ODRMMTkuMjgzMiAxOS43NTIzQzE5LjIyNjQgMTkuNzE2NCAxOS4xNjI2IDE5LjY4NjIgMTkuMDkxOSAxOS42NjE5QzE5LjAyMjMgMTkuNjM3NSAxOC45NDE4IDE5LjYyNTQgMTguODUwMiAxOS42MjU0QzE4LjcwNjQgMTkuNjI1NCAxOC42MDA5IDE5LjY2MjUgMTguNTMzNyAxOS43MzY3QzE4LjQ2NzYgMTkuODA5NyAxOC40MzQ2IDE5LjkxNjMgMTguNDM0NiAyMC4wNTY2VjIwLjI0MDlDMTguNDM0NiAyMC4zODAxIDE4LjQ2ODggMjAuNDg3MyAxOC41MzcyIDIwLjU2MjZDMTguNjA2NyAyMC42MzggMTguNzEzNCAyMC42NzU3IDE4Ljg1NzEgMjAuNjc1N0MxOC45NDg3IDIwLjY3NTcgMTkuMDI5OSAyMC42NjQxIDE5LjEwMDYgMjAuNjQwOUMxOS4xNzEzIDIwLjYxNjYgMTkuMjM3NCAyMC41ODcgMTkuMjk4OCAyMC41NTIyTDE5LjI2MjMgMjAuOTI3OEMxOS4yMDU1IDIwLjk2MDMgMTkuMTM0MiAyMC45ODg3IDE5LjA0ODQgMjEuMDEzQzE4Ljk2MjYgMjEuMDM4NSAxOC44NjcgMjEuMDUxMyAxOC43NjE1IDIxLjA1MTNaTTE5Ljc2NjIgMjEuMDA3OFYxOC43MDg5SDIwLjIxMzFWMjEuMDA3OEgxOS43NjYyWk0yMC43ODU4IDIxLjAwNzhWMTkuMzAxOUgyMS4yMzA5VjIxLjAwNzhIMjAuNzg1OFpNMjEuMDA4NCAxOS4xMDAyQzIwLjkyMzcgMTkuMTAwMiAyMC44NjExIDE5LjA4MDUgMjAuODIwNSAxOS4wNDExQzIwLjc4MTEgMTkuMDAwNSAyMC43NjE0IDE4Ljk0NDkgMjAuNzYxNCAxOC44NzQxVjE4Ljg2NTRDMjAuNzYxNCAxOC43OTQ3IDIwLjc4MTEgMTguNzM5MSAyMC44MjA1IDE4LjY5ODVDMjAuODYxMSAxOC42NTc5IDIwLjkyMzcgMTguNjM3NiAyMS4wMDg0IDE4LjYzNzZDMjEuMDkxOCAxOC42Mzc2IDIxLjE1MzggMTguNjU3OSAyMS4xOTQ0IDE4LjY5ODVDMjEuMjM1IDE4LjczOTEgMjEuMjU1MyAxOC43OTQ3IDIxLjI1NTMgMTguODY1NFYxOC44NzQxQzIxLjI1NTMgMTguOTQ2IDIxLjIzNSAxOS4wMDE3IDIxLjE5NDQgMTkuMDQxMUMyMS4xNTM4IDE5LjA4MDUgMjEuMDkxOCAxOS4xMDAyIDIxLjAwODQgMTkuMTAwMlpNMjIuMzk1IDE4LjY1NUMyMi40ODU0IDE4LjY1NSAyMi41Njg5IDE4LjY2MjYgMjIuNjQ1NCAxOC42Nzc2QzIyLjcyMTkgMTguNjkyNyAyMi43ODk4IDE4LjcxMTMgMjIuODQ4OSAxOC43MzMzTDIyLjg4ODkgMTkuMDY3MkMyMi44MzkgMTkuMDUyMSAyMi43ODY5IDE5LjAzOTkgMjIuNzMyNCAxOS4wMzA2QzIyLjY3OSAxOS4wMjAyIDIyLjYxOTkgMTkuMDE1IDIyLjU1NSAxOS4wMTVDMjIuNDc3MyAxOS4wMTUgMjIuNDE1OSAxOS4wMjM3IDIyLjM3MDcgMTkuMDQxMUMyMi4zMjY2IDE5LjA1ODUgMjIuMjk1MyAxOS4wODM0IDIyLjI3NjggMTkuMTE1OUMyMi4yNTgyIDE5LjE0ODMgMjIuMjQ4OSAxOS4xODY2IDIyLjI0ODkgMTkuMjMwNlYxOS4yMzU4QzIyLjI0ODkgMTkuMjY3MSAyMi4yNTM2IDE5LjI5NjcgMjIuMjYyOSAxOS4zMjQ1QzIyLjI3MjEgMTkuMzUyNCAyMi4yODMxIDE5LjM3NzMgMjIuMjk1OSAxOS4zOTkzTDIyLjAwNTUgMTkuNDA5N1YxOS4zNjI4QzIxLjk1MjIgMTkuMzM3MyAyMS45MDcgMTkuMjk5NiAyMS44Njk5IDE5LjI0OThDMjEuODMyOCAxOS4xOTk5IDIxLjgxNDIgMTkuMTM3OSAyMS44MTQyIDE5LjA2MzdWMTkuMDU1QzIxLjgxNDIgMTguOTMyMSAyMS44NjEyIDE4LjgzNDcgMjEuOTU1MSAxOC43NjI5QzIyLjA1MDEgMTguNjkxIDIyLjE5NjggMTguNjU1IDIyLjM5NSAxOC42NTVaTTIxLjg3MzMgMjEuMDA3OFYxOS41MDE5SDIyLjMxNjhWMjEuMDA3OEgyMS44NzMzWk0yMS42MjY0IDE5LjcyOFYxOS4zNzVMMjIuMDU3NyAxOS4zNzg0TDIyLjIzMTYgMTkuMzc1SDIyLjg4NTRMMjIuODQ3MSAxOS43MjhIMjEuNjI2NFpNMjMuNTExNCAxOC42NTVDMjMuNjAxOCAxOC42NTUgMjMuNjg1MyAxOC42NjI2IDIzLjc2MTggMTguNjc3NkMyMy44MzgzIDE4LjY5MjcgMjMuOTA2MSAxOC43MTEzIDIzLjk2NTMgMTguNzMzM0wyNC4wMDUzIDE5LjA2NzJDMjMuOTU1NCAxOS4wNTIxIDIzLjkwMzIgMTkuMDM5OSAyMy44NDg4IDE5LjAzMDZDMjMuNzk1NCAxOS4wMjAyIDIzLjczNjMgMTkuMDE1IDIzLjY3MTQgMTkuMDE1QzIzLjU5MzcgMTkuMDE1IDIzLjUzMjMgMTkuMDIzNyAyMy40ODcxIDE5LjA0MTFDMjMuNDQzIDE5LjA1ODUgMjMuNDExNyAxOS4wODM0IDIzLjM5MzIgMTkuMTE1OUMyMy4zNzQ2IDE5LjE0ODMgMjMuMzY1MyAxOS4xODY2IDIzLjM2NTMgMTkuMjMwNlYxOS4yMzU4QzIzLjM2NTMgMTkuMjY3MSAyMy4zNyAxOS4yOTY3IDIzLjM3OTMgMTkuMzI0NUMyMy4zODg1IDE5LjM1MjQgMjMuMzk5NSAxOS4zNzczIDIzLjQxMjMgMTkuMzk5M0wyMy4xMjE5IDE5LjQwOTdWMTkuMzYyOEMyMy4wNjg2IDE5LjMzNzMgMjMuMDIzMyAxOS4yOTk2IDIyLjk4NjMgMTkuMjQ5OEMyMi45NDkyIDE5LjE5OTkgMjIuOTMwNiAxOS4xMzc5IDIyLjkzMDYgMTkuMDYzN1YxOS4wNTVDMjIuOTMwNiAxOC45MzIxIDIyLjk3NzYgMTguODM0NyAyMy4wNzE1IDE4Ljc2MjlDMjMuMTY2NSAxOC42OTEgMjMuMzEzMiAxOC42NTUgMjMuNTExNCAxOC42NTVaTTIyLjk4OTcgMjEuMDA3OFYxOS41MDE5SDIzLjQzMzJWMjEuMDA3OEgyMi45ODk3Wk0yMi43NDI4IDE5LjcyOFYxOS4zNzVMMjMuMTc0MSAxOS4zNzg0TDIzLjM0OCAxOS4zNzVIMjQuMDAxOEwyMy45NjM1IDE5LjcyOEgyMi43NDI4WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEyLjgyOTkgMjQuODQ0MlYyNC4yNzg4SDEzLjQzMkMxMy42MDQ2IDI0LjI3ODggMTMuNzM0MSAyNC4yMzIgMTMuODIwNSAyNC4xMzg1QzEzLjkwNjggMjQuMDQ1IDEzLjk1IDIzLjkxMTIgMTMuOTUgMjMuNzM3MVYyMy4xODQ2QzEzLjk1IDIzLjAxMDUgMTMuOTA2OCAyMi44Nzc0IDEzLjgyMDUgMjIuNzg1M0MxMy43MzQxIDIyLjY5MTggMTMuNjA0NiAyMi42NDUxIDEzLjQzMiAyMi42NDUxSDEyLjgyNzdWMjIuMDg2MUgxMy40NTU3QzEzLjg0ODUgMjIuMDg2MSAxNC4xNDIgMjIuMTgxIDE0LjMzNjMgMjIuMzcxQzE0LjUzMTkgMjIuNTU5NCAxNC42Mjk4IDIyLjgzMDcgMTQuNjI5OCAyMy4xODQ2VjIzLjczOTJDMTQuNjI5OCAyNC4wOTQ2IDE0LjUzMjcgMjQuMzY4IDE0LjMzODQgMjQuNTU5NEMxNC4xNDQyIDI0Ljc0OTMgMTMuODUgMjQuODQ0MiAxMy40NTU3IDI0Ljg0NDJIMTIuODI5OVpNMTIuMzU3MiAyNC44NDQyVjIyLjA4NjFIMTMuMDIxOVYyNC44NDQySDEyLjM1NzJaTTE1LjIxNTMgMjQuODQ0MkwxNS4zMDE2IDIyLjA4NjFIMTYuMjQyNkwxNi42OTc5IDIzLjczMDZIMTYuNzI2TDE3LjE4MTQgMjIuMDg2MUgxOC4xMjIzTDE4LjIwODcgMjQuODQ0MkgxNy41NTQ3TDE3LjUzNTMgMjMuOTRMMTcuNTE1OSAyMi44MzkzSDE3LjQ4MzVMMTcuMDEwOSAyNC41MjA1SDE2LjQxMDlMMTUuOTM4MiAyMi44MzkzSDE1LjkwMzdMMTUuODg2NSAyMy45NDIxTDE1Ljg2OTIgMjQuODQ0MkgxNS4yMTUzWk0xOS40MDA3IDI0Ljg0NDJMMTguNjQ1NCAyMi4wODYxSDE5LjM0NDZMMTkuODg2MyAyNC4zNDE0SDE5LjkzNkwyMC40Nzk4IDIyLjA4NjFIMjEuMTc2OUwyMC40MjM3IDI0Ljg0NDJIMTkuNDAwN1oiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTE2LjY2OTcgMi43MjMxNEwxNi4zNTgxIDMuMzUyODNMMTUuNjYyOCAzLjQ1NDVMMTYuMTY0NiAzLjk0NjQ0TDE2LjA0NjYgNC42NDE3M0wxNi42Njk3IDQuMzEzNzZMMTcuMjkyOCA0LjY0MTczTDE3LjE3NDcgMy45NDY0NEwxNy42NzY1IDMuNDU0NUwxNi45ODEzIDMuMzUyODNMMTYuNjY5NyAyLjcyMzE0WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjEuMjExOSAzLjU0MzQ2TDIwLjcwMzUgNC4wMjg4NEwyMC4wMTQ4IDMuODg0NTRMMjAuMzE5OCA0LjUyMDc5TDE5Ljk3MjIgNS4xMzA4TDIwLjY2NzQgNS4wMzU2OUwyMS4xNDMgNS41NTcxNUwyMS4yNjc2IDQuODY1MTVMMjEuOTEwNCA0LjU3NjU0TDIxLjI5MDYgNC4yNDIwMkwyMS4yMTE5IDMuNTQzNDZaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0yNS4yMDMyIDUuODY1NTRMMjQuNTU3MSA2LjE1MDg3TDIzLjk2MDIgNS43ODAyN0wyNC4wMjkxIDYuNDc4ODNMMjMuNDkxMiA2LjkzNDdMMjQuMTc5OSA3LjA4NTU2TDI0LjQ0NTYgNy43MzQ5M0wyNC43OTk4IDcuMTI4MkwyNS41MDE2IDcuMDc1NzNMMjUuMDM1OSA2LjU1MDk5TDI1LjIwMzIgNS44NjU1NFoiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTI4LjE1NDkgOS40MTM3NUwyNy40NTMxIDkuNDU5NjdMMjcuMDE2OSA4LjkwODY5TDI2Ljg0MyA5LjU5MDg1TDI2LjE4MzggOS44MzM1NUwyNi43Nzc0IDEwLjIwNzRMMjYuODA3IDEwLjkxMjVMMjcuMzQ4MSAxMC40NjMyTDI4LjAyMzcgMTAuNjUzNUwyNy43NjQ2IDEwLjAwMDhMMjguMTU0OSA5LjQxMzc1WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjkuNzE1OSAxMy43NTk0TDI5LjA0MzYgMTMuNTYyNkwyOC44MjA2IDEyLjg5MzZMMjguNDIzOCAxMy40NzRMMjcuNzIxOSAxMy40NzczTDI4LjE1MTYgMTQuMDM0OUwyNy45Mzg0IDE0LjcwMzlMMjguNjAwOSAxNC40Njc4TDI5LjE3MTUgMTQuODc3N0wyOS4xNTE4IDE0LjE3NTlMMjkuNzE1OSAxMy43NTk0WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjkuNjk5NyAxOC4zNzM3TDI5LjEzMjMgMTcuOTU3MkwyOS4xNTUzIDE3LjI1NTRMMjguNTgxMyAxNy42NjUzTDI3LjkxODggMTcuNDI5MkwyOC4xMzUzIDE4LjA5ODJMMjcuNzAyNCAxOC42NTU4TDI4LjQwNzUgMTguNjU5MUwyOC44MDQzIDE5LjIzOTVMMjkuMDI0MSAxOC41NzA1TDI5LjY5OTcgMTguMzczN1oiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTI4LjEwMjQgMjIuNzA2M0wyNy43MTIxIDIyLjEyMjVMMjcuOTc0NSAyMS40NjY2TDI3LjI5NTYgMjEuNjYwMUwyNi43NTQ1IDIxLjIwNzVMMjYuNzI4MiAyMS45MTI2TDI2LjEzMTMgMjIuMjg2NUwyNi43OTM4IDIyLjUzMjVMMjYuOTY0NCAyMy4yMTQ2TDI3LjQwMDYgMjIuNjYwNEwyOC4xMDI0IDIyLjcwNjNaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0yNS4xMjQ2IDI2LjIzMTdMMjQuOTU3MyAyNS41NDk1TDI1LjQyMyAyNS4wMjE1TDI0LjcyMTIgMjQuOTY5TDI0LjM2NyAyNC4zNjIzTDI0LjEwMTMgMjUuMDE0OUwyMy40MTI2IDI1LjE2MjVMMjMuOTUwNSAyNS42MTg0TDIzLjg4MTYgMjYuMzIwMkwyNC40Nzg1IDI1Ljk0OTZMMjUuMTI0NiAyNi4yMzE3WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjEuMTE3IDI4LjUyNDRMMjEuMTkyNCAyNy44MjU5TDIxLjgxMjMgMjcuNDkxM0wyMS4xNzI4IDI3LjIwMjdMMjEuMDQ0OCAyNi41MTA3TDIwLjU3MjYgMjcuMDMyMkwxOS44NzQgMjYuOTM3MUwyMC4yMjQ5IDI3LjU0NzFMMTkuOTE5OSAyOC4xODAxTDIwLjYwODcgMjguMDM5TDIxLjExNyAyOC41MjQ0WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMTYuNTY4IDI5LjMwNzlMMTYuODc5NiAyOC42NzgyTDE3LjU3NDggMjguNTc2NUwxNy4wNjk4IDI4LjA4NDZMMTcuMTkxMSAyNy4zOTI2TDE2LjU2OCAyNy43MjA1TDE1Ljk0NDkgMjcuMzkyNkwxNi4wNjI5IDI4LjA4NDZMMTUuNTU3OSAyOC41NzY1TDE2LjI1NjQgMjguNjc4MkwxNi41NjggMjkuMzA3OVoiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTEyLjAyMjQgMjguNDg4M0wxMi41MzA4IDI4LjAwMjlMMTMuMjE5NSAyOC4xNDcyTDEyLjkxNDUgMjcuNTExTDEzLjI2NTQgMjYuOTAxTDEyLjU2NjggMjYuOTk2MUwxMi4wOTQ2IDI2LjQ3NDZMMTEuOTY2NyAyNy4xNjY2TDExLjMyNzEgMjcuNDU4NUwxMS45NDcgMjcuNzg5N0wxMi4wMjI0IDI4LjQ4ODNaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik04LjAzNDM5IDI2LjE2NjNMOC42NzcyIDI1Ljg4NDJMOS4yNzczNyAyNi4yNTE1TDkuMjA1MjIgMjUuNTUzTDkuNzQzMDggMjUuMDk3MUw5LjA1NDM1IDI0Ljk0OTVMOC43ODg3IDI0LjI5NjlMOC40MzQ1MSAyNC45MDM2TDcuNzMyNjcgMjQuOTU2MUw4LjIwMTY1IDI1LjQ4MDhMOC4wMzQzOSAyNi4xNjYzWiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNNS4wNzk1OSAyMi42MTc0TDUuNzgxNDMgMjIuNTcxNUw2LjIxNzYyIDIzLjEyNThMNi4zOTE0NCAyMi40NDM2TDcuMDUwNjQgMjIuMTk3Nkw2LjQ1NzAzIDIxLjgyMzhMNi40MzA3OSAyMS4xMTg3TDUuODg5NjYgMjEuNTY4TDUuMjEwNzcgMjEuMzc3N0w1LjQ2OTg2IDIyLjAzMzdMNS4wNzk1OSAyMi42MTc0WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMy41MTgzMSAxOC4yNzIyTDQuMTkzOTEgMTguNDcyMkw0LjQxMzY1IDE5LjEzOEw0LjgxMDQ4IDE4LjU1NzVMNS41MTU2IDE4LjU1NDJMNS4wODI2OSAxNy45OTY3TDUuMjk5MTUgMTcuMzI3Nkw0LjYzNjY2IDE3LjU2MzhMNC4wNjI3MyAxNy4xNTM4TDQuMDg1NjkgMTcuODU4OUwzLjUxODMxIDE4LjI3MjJaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zLjUzNDkxIDEzLjY1NzhMNC4xMDIyOSAxNC4wNzQzTDQuMDgyNjEgMTQuNzc2Mkw0LjY1MzI2IDE0LjM2NjJMNS4zMTU3NSAxNC42MDIzTDUuMTAyNTcgMTMuOTMzM0w1LjUzMjIgMTMuMzc1OEw0LjgyNzA4IDEzLjM3MjVMNC40MzM1MyAxMi43OTJMNC4yMTA1MSAxMy40NjFMMy41MzQ5MSAxMy42NTc4WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNNS4xMzIwOCA5LjMyNTI0TDUuNTIyMzYgOS45MDkwMUw1LjI2MzI3IDEwLjU2NDlMNS45Mzg4NyAxMC4zNzQ3TDYuNDgwMDEgMTAuODI0TDYuNTA5NTIgMTAuMTE4OUw3LjEwMzEzIDkuNzQ1MDNMNi40NDM5MyA5LjQ5OTA2TDYuMjcwMTEgOC44MTY4OUw1LjgzMzkyIDkuMzcxMTVMNS4xMzIwOCA5LjMyNTI0WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNOC4xMTMyNSA1Ljc5OTYzTDguMjgwNTEgNi40ODUwN0w3LjgxMTUyIDcuMDA5ODFMOC41MTMzNiA3LjA2MjI4TDguODY3NTYgNy42NjkwMUw5LjEzNjQ5IDcuMDE2MzdMOS44MjE5MyA2Ljg2ODc4TDkuMjg0MDcgNi40MTI5Mkw5LjM1NjIzIDUuNzE0MzZMOC43NTYwNiA2LjA4MTY3TDguMTEzMjUgNS43OTk2M1oiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTEyLjEyMDkgMy41MDczMkwxMi4wNDIyIDQuMjA1ODhMMTEuNDIyNCA0LjU0MDQxTDEyLjA2NTIgNC44MjkwMUwxMi4xODk4IDUuNTIxMDFMMTIuNjY1MyA0Ljk5OTU1TDEzLjM2MDYgNS4wOTQ2NkwxMy4wMTMgNC40ODQ2NUwxMy4zMTggMy44NTE2OEwxMi42MjkzIDMuOTkyNzFMMTIuMTIwOSAzLjUwNzMyWiIgZmlsbD0iI0RDODU0OCIvPgo8L3N2Zz4Kamlzc3VlckxvZ2+iZmZvcm1hdGNzdmdkZGF0YVmBojxzdmcgd2lkdGg9IjE0NSIgaGVpZ2h0PSI0MiIgdmlld0JveD0iMCAwIDE0NSA0MiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIwLjk2OTkgNDEuOTdDMzIuNTUxMiA0MS45NyA0MS45Mzk3IDMyLjU4MTUgNDEuOTM5NyAyMS4wMDAxQzQxLjkzOTcgOS40MTg4IDMyLjU1MTIgMC4wMzAyNzM0IDIwLjk2OTkgMC4wMzAyNzM0QzkuMzg4NTMgMC4wMzAyNzM0IDAgOS40MTg4IDAgMjEuMDAwMUMwIDMyLjU4MTUgOS4zODg1MyA0MS45NyAyMC45Njk5IDQxLjk3WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjAuOTA1MyAzOS42OTU0QzMxLjIxOTIgMzkuNjk1NCAzOS41ODAyIDMxLjMzNDQgMzkuNTgwMiAyMS4wMjA2QzM5LjU4MDIgMTAuNzA2NyAzMS4yMTkyIDIuMzQ1NyAyMC45MDUzIDIuMzQ1N0MxMC41OTE1IDIuMzQ1NyAyLjIzMDQ3IDEwLjcwNjcgMi4yMzA0NyAyMS4wMjA2QzIuMjMwNDcgMzEuMzM0NCAxMC41OTE1IDM5LjY5NTQgMjAuOTA1MyAzOS42OTU0WiIgZmlsbD0iIzAwMzQ1OSIvPgo8cGF0aCBkPSJNMjAuOTA1NCAxMy42NzA5TDMyLjMyNzUgMTQuMzA4OUwyMC45MDU0IDE0Ljk0N0w5LjQ4MzQgMTQuMzA4OUwyMC45MDU0IDEzLjY3MDlaIiBmaWxsPSIjMEE0Qjc3Ii8+CjxwYXRoIGQ9Ik0yMC45MDU0IDI4LjE5NjNMOS40ODMzNCAyNy41NTgyTDIwLjkwNTQgMjYuOTIwMkwzMi4zMjc0IDI3LjU1ODJMMjAuOTA1NCAyOC4xOTYzWiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMjAuOTA1MyAxMS42MTUyTDMwLjA2MjggMTIuMjUzM0wyMC45MDUzIDEyLjg5MTNMMTEuNzQ3OCAxMi4yNTMzTDIwLjkwNTMgMTEuNjE1MloiIGZpbGw9IiMwQTRCNzciLz4KPHBhdGggZD0iTTIwLjkwNTIgMzAuMjUyTDExLjc0NzggMjkuNjEzOUwyMC45MDUyIDI4Ljk3NTlMMzAuMDYyNyAyOS42MTM5TDIwLjkwNTIgMzAuMjUyWiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMjAuOTA1MyA5LjU2MDA2TDI3LjQ4NDUgMTAuMTk4MUwyMC45MDUzIDEwLjgzNjFMMTQuMzI2MiAxMC4xOTgxTDIwLjkwNTMgOS41NjAwNloiIGZpbGw9IiMwQTRCNzciLz4KPHBhdGggZD0iTTIwLjkwNTIgMzIuMzA3MUwxNC4zMjYgMzEuNjY5MUwyMC45MDUyIDMxLjAzMUwyNy40ODQ0IDMxLjY2OTFMMjAuOTA1MiAzMi4zMDcxWiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMzguNzYzOCAyMC43MTQ5QzM4Ljc2MzggMzAuNTc1IDMwLjc2NjIgMzguNTY3OCAyMC45MDYxIDM4LjU2NzhDMTEuMDQ2IDM4LjU2NzggMy4wNTMyMiAzMC41NzUgMy4wNTMyMiAyMC43MTQ5QzMuMDUzMjIgMjAuMDMzMyAzLjA5MTYzIDE5LjM2MTIgMy4xNjg0MyAxOC42OTg3QzQuMTcxNzMgMjcuNjA4NCAxMS43Mjc2IDM0LjUzMDYgMjAuOTA2MSAzNC41MzA2QzMwLjA4NDYgMzQuNTMwNiAzNy42NDUzIDI3LjYwODQgMzguNjQ4NiAxOC42OTg3QzM4LjcyNTQgMTkuMzYxMiAzOC43NjM4IDIwLjAzMzMgMzguNzYzOCAyMC43MTQ5WiIgZmlsbD0iIzAwMkQ0RCIvPgo8cGF0aCBkPSJNMTcuNzQ5NyAyMS4xNzc0TDE0LjY4NzQgMTguNzA2M0MxNC42MDMyIDE4LjYzODYgMTQuNTU0MiAxOC41MzY0IDE0LjU1NDIgMTguNDI4M0wxNC41NTQyIDE2LjUxMjZDMTQuNTU0MiAxNi4zNzM2IDE0LjYzNTIgMTYuMjQ2OSAxNC43NjE0IDE2LjE4ODNMMTcuMzI0NyAxNC45OTk2QzE3LjUxIDE0LjkxMzggMTcuNTg1NiAxNC42OTAxIDE3LjQ5MDggMTQuNTA5MUwxNi42Mzk4IDEyLjg4ODVDMTYuNjEyNiAxMi44MzczIDE2LjU5ODggMTIuNzgwMyAxNi41OTg4IDEyLjcyMjNMMTYuNTk4OCAxMS44NzIzQzE2LjU5ODggMTEuNzQ2MSAxNi42NjU5IDExLjYyODkgMTYuNzc0NSAxMS41NjQ1TDIyLjAxMjkgOC40NzI4OUMyMi4xNDEzIDguMzk3MjcgMjIuMzAzMiA4LjQwODk4IDIyLjQxOTMgOC41MDI3MkwyNy4zODU1IDEyLjUxNDFMMjcuMzg1NSAxNS4yNjUzQzI3LjM4NTUgMTUuNDYyOSAyNy4yMjU3IDE1LjYyMjcgMjcuMDI4MSAxNS42MjI3TDI0LjIwNTYgMTUuNzk4M0MyNC4wMDggMTUuNzk4MyAyMy44NDgyIDE1Ljk1OCAyMy44NDgyIDE2LjE1NTZMMjMuNjE1NCAxOC44MTA3QzIzLjYxNTQgMTkuMDA4MyAyMy43NzUyIDE5LjE2OCAyMy45NzI4IDE5LjE2OEMyNC4xNzA0IDE5LjE2OCAyNC4zMzAxIDE5LjMyNzggMjQuMzMwMSAxOS41MjU0TDI0LjMzMDEgMjEuOTQ1NEMyNC4zMzAxIDIyLjE0MyAyNC4xNzA0IDIyLjMwMjggMjMuOTcyOCAyMi4zMDI4TDIxLjU1MTIgMjIuMzAyOEMyMS41MTY1IDIyLjMwMjggMjEuNDgxOSAyMi4yOTc0IDIxLjQ0ODQgMjIuMjg3OEwxNy43NDkxIDIxLjE3NzQiIGZpbGw9IiMwMEE4RTgiLz4KPHBhdGggZD0iTTI1LjY4NzkgMTkuNDI1NUwyNi40NjggMTguNTQ1OUMyNi41MDM4IDE4LjUwNTQgMjYuNTE5OSAxOC40NTExIDI2LjUxMTkgMTguMzk3NkwyNi4yNzE3IDE2LjgwMDVDMjYuMjYwOSAxNi43Mjg3IDI2LjIwODYgMTYuNjcwMiAyNi4xMzg1IDE2LjY1MTRMMjUuMzU1OSAxNi40NDE3QzI1LjI1ODYgMTYuNDE1NiAyNS4xNTg1IDE2LjQ3MzQgMjUuMTMyNCAxNi41NzA3TDI0LjYxOCAxOC40OTA3QzI0LjU5MTkgMTguNTg4MSAyNC42NDk3IDE4LjY4ODEgMjQuNzQ3IDE4LjcxNDJMMjUuMTY0MiAxOC44MjZDMjUuMjMxIDE4Ljg0MzkgMjUuMjgxOSAxOC44OTggMjUuMjk1OCAxOC45NjU3TDI1LjM3MjYgMTkuMzQxQzI1LjQwMjcgMTkuNDg4MyAyNS41ODgxIDE5LjUzOCAyNS42ODc5IDE5LjQyNTVaIiBmaWxsPSIjMDBBOEU4Ii8+CjxwYXRoIGQ9Ik0xMS42NTA2IDI3LjMwMjdMMTEuNzQ1NCAyNC40MTY2SDEyLjU5OUwxMy4xNDEgMjYuMjUyNkgxMy4xNzA0TDEzLjcxMDEgMjQuNDE2NkgxNC41NjE1TDE0LjY1ODYgMjcuMzAyN0gxNC4wODVMMTQuMDU3OSAyNi4yNjM5TDE0LjAzMzEgMjUuMDY0OEgxMy45OTY5TDEzLjQzMDEgMjYuOTY2MkgxMi44NzkxTDEyLjMxIDI1LjA2NDhIMTIuMjczOEwxMi4yNDkgMjYuMjY2MkwxMi4yMjQyIDI3LjMwMjdIMTEuNjUwNlpNMTYuMzM4NCAyNy4zNjM3QzE1Ljk5NTEgMjcuMzYzNyAxNS43MzQ3IDI3LjI3NTYgMTUuNTU3IDI3LjA5OTVDMTUuMzgwOSAyNi45MjMzIDE1LjI5MjggMjYuNjcyNyAxNS4yOTI4IDI2LjM0NzVWMjYuMDQ3MUMxNS4yOTI4IDI1LjcyMDQgMTUuMzgwOSAyNS40NjkgMTUuNTU3IDI1LjI5MjlDMTUuNzM0NyAyNS4xMTUyIDE1Ljk5NTEgMjUuMDI2NCAxNi4zMzg0IDI1LjAyNjRDMTYuNjgwMSAyNS4wMjY0IDE2LjkzOTEgMjUuMTE1MiAxNy4xMTUyIDI1LjI5MjlDMTcuMjkxNCAyNS40NjkgMTcuMzc5NSAyNS43MjA0IDE3LjM3OTUgMjYuMDQ3MVYyNi4zNDc1QzE3LjM3OTUgMjYuNjcyNyAxNy4yOTE0IDI2LjkyMzMgMTcuMTE1MiAyNy4wOTk1QzE2Ljk0MDYgMjcuMjc1NiAxNi42ODE2IDI3LjM2MzcgMTYuMzM4NCAyNy4zNjM3Wk0xNi4zMzg0IDI2LjkwMDhDMTYuNDg4OSAyNi45MDA4IDE2LjYwMzQgMjYuODU1NiAxNi42ODE2IDI2Ljc2NTNDMTYuNzYxNCAyNi42NzQ5IDE2LjgwMTMgMjYuNTQ1NSAxNi44MDEzIDI2LjM3NjhWMjYuMDE3OEMxNi44MDEzIDI1Ljg0NjEgMTYuNzYxNCAyNS43MTUyIDE2LjY4MTYgMjUuNjI0OEMxNi42MDM0IDI1LjUzMyAxNi40ODg5IDI1LjQ4NzEgMTYuMzM4NCAyNS40ODcxQzE2LjE4NjMgMjUuNDg3MSAxNi4wNzA0IDI1LjUzMyAxNS45OTA2IDI1LjYyNDhDMTUuOTEyMyAyNS43MTUyIDE1Ljg3MzIgMjUuODQ2MSAxNS44NzMyIDI2LjAxNzhWMjYuMzc2OEMxNS44NzMyIDI2LjU0NTUgMTUuOTEyMyAyNi42NzQ5IDE1Ljk5MDYgMjYuNzY1M0MxNi4wNzA0IDI2Ljg1NTYgMTYuMTg2MyAyNi45MDA4IDE2LjMzODQgMjYuOTAwOFpNMTkuNDcxMSAyNy4zMDI3VjI1Ljk3MjZDMTkuNDcxMSAyNS44NzkzIDE5LjQ1ODMgMjUuNzk4NyAxOS40MzI3IDI1LjczMUMxOS40MDg3IDI1LjY2MzIgMTkuMzY4IDI1LjYxMDUgMTkuMzEwOCAyNS41NzI5QzE5LjI1MzYgMjUuNTM1MiAxOS4xNzUzIDI1LjUxNjQgMTkuMDc1OSAyNS41MTY0QzE4Ljk4ODYgMjUuNTE2NCAxOC45MTE4IDI1LjUzMjIgMTguODQ1NiAyNS41NjM4QzE4Ljc4MDggMjUuNTk1NSAxOC43Mjc0IDI1LjYzODQgMTguNjg1MiAyNS42OTI2QzE4LjY0NDYgMjUuNzQ1MyAxOC42MTM3IDI1LjgwNTUgMTguNTkyNyAyNS44NzMyTDE4LjUwMjMgMjUuNTU3MUgxOC42MTA3QzE4LjYzNDggMjUuNDU5MiAxOC42NzQ3IDI1LjM3MTEgMTguNzMwNCAyNS4yOTI5QzE4Ljc4NzYgMjUuMjE0NiAxOC44NjQ0IDI1LjE1MjggMTguOTYwOCAyNS4xMDc3QzE5LjA1ODYgMjUuMDYxIDE5LjE4MDYgMjUuMDM3NyAxOS4zMjY2IDI1LjAzNzdDMTkuNDk2NyAyNS4wMzc3IDE5LjYzNDUgMjUuMDcgMTkuNzM5OSAyNS4xMzQ4QzE5Ljg0NTMgMjUuMTk4IDE5LjkyMjggMjUuMjkyOSAxOS45NzI1IDI1LjQxOTNDMjAuMDIzNyAyNS41NDU4IDIwLjA0OTMgMjUuNzAyNCAyMC4wNDkzIDI1Ljg4OVYyNy4zMDI3SDE5LjQ3MTFaTTE4LjAyODEgMjcuMzAyN1YyNS4wODczSDE4LjYwNjJMMTguNTgzNiAyNS42MjcxTDE4LjYwNjIgMjUuNjc0NVYyNy4zMDI3SDE4LjAyODFaTTIxLjYwODUgMjcuMzUwMkMyMS40MjAzIDI3LjM1MDIgMjEuMjY5OCAyNy4zMjIzIDIxLjE1NjkgMjcuMjY2NkMyMS4wNDU1IDI3LjIwOTQgMjAuOTY0OSAyNy4xMjM2IDIwLjkxNTIgMjcuMDA5MkMyMC44NjU2IDI2Ljg5NDcgMjAuODQwNyAyNi43NTQgMjAuODQwNyAyNi41ODY5VjI1LjI5NTFIMjEuNDE0M1YyNi41MDFDMjEuNDE0MyAyNi42MjE1IDIxLjQ0MTQgMjYuNzEwMyAyMS40OTU2IDI2Ljc2NzVDMjEuNTUxMyAyNi44MjMyIDIxLjY0ODQgMjYuODUxMSAyMS43ODY5IDI2Ljg1MTFDMjEuODY4MiAyNi44NTExIDIxLjk0NjUgMjYuODQyOCAyMi4wMjE4IDI2LjgyNjJDMjIuMDk3MSAyNi44MDgyIDIyLjE2NjMgMjYuNzg0OCAyMi4yMjk2IDI2Ljc1NjJMMjIuMTc5OSAyNy4yMzk1QzIyLjEwNDYgMjcuMjc0MSAyMi4wMTggMjcuMzAxMiAyMS45MjAyIDI3LjMyMDhDMjEuODIzOCAyNy4zNDA0IDIxLjcxOTkgMjcuMzUwMiAyMS42MDg1IDI3LjM1MDJaTTIwLjUyIDI1LjU2MTZWMjUuMTA1NEgyMi4yMDkyTDIyLjE1OTYgMjUuNTYxNkgyMC41MlpNMjAuODQ3NSAyNS4xNDgzTDIwLjg0NTIgMjQuNTYxMkwyMS40MjExIDI0LjUwMjVMMjEuMzk4NSAyNS4xNDgzSDIwLjg0NzVaTTIzLjY5MDYgMjcuMzU5MkMyMy4zNDg5IDI3LjM1OTIgMjMuMDk1MiAyNy4yNjg5IDIyLjkyOTYgMjcuMDg4MkMyMi43NjU1IDI2LjkwNzUgMjIuNjgzNCAyNi42NTM5IDIyLjY4MzQgMjYuMzI3MlYyNi4wNTYyQzIyLjY4MzQgMjUuNzMxIDIyLjc2NjIgMjUuNDc4OCAyMi45MzE4IDI1LjI5OTZDMjMuMDk3NCAyNS4xMjA1IDIzLjM1MDQgMjUuMDMwOSAyMy42OTA2IDI1LjAzMDlDMjMuNzc5NCAyNS4wMzA5IDIzLjg2MjIgMjUuMDM4NCAyMy45MzkgMjUuMDUzNUMyNC4wMTczIDI1LjA2NyAyNC4wODgxIDI1LjA4NTggMjQuMTUxMyAyNS4xMDk5QzI0LjIxNiAyNS4xMzQgMjQuMjcyNSAyNS4xNTk2IDI0LjMyMDcgMjUuMTg2N0wyNC4zNjgxIDI1LjY3MjJDMjQuMjk0MyAyNS42MjU2IDI0LjIxMTUgMjUuNTg2NCAyNC4xMTk3IDI1LjU1NDhDMjQuMDI5NCAyNS41MjMyIDIzLjkyNDcgMjUuNTA3NCAyMy44MDU4IDI1LjUwNzRDMjMuNjE5MSAyNS41MDc0IDIzLjQ4MjEgMjUuNTU1NiAyMy4zOTQ4IDI1LjY1MTlDMjMuMzA5IDI1Ljc0NjggMjMuMjY2MSAyNS44ODUzIDIzLjI2NjEgMjYuMDY3NFYyNi4zMDY4QzIzLjI2NjEgMjYuNDg3NSAyMy4zMTA1IDI2LjYyNjggMjMuMzk5MyAyNi43MjQ2QzIzLjQ4OTYgMjYuODIyNSAyMy42MjgxIDI2Ljg3MTQgMjMuODE0OCAyNi44NzE0QzIzLjkzMzggMjYuODcxNCAyNC4wMzkxIDI2Ljg1NjMgMjQuMTMxIDI2LjgyNjJDMjQuMjIyOCAyNi43OTQ2IDI0LjMwODYgMjYuNzU2MiAyNC4zODg0IDI2LjcxMTFMMjQuMzQxIDI3LjE5ODlDMjQuMjY3MiAyNy4yNDEgMjQuMTc0NiAyNy4yNzc5IDI0LjA2MzIgMjcuMzA5NUMyMy45NTE4IDI3LjM0MjYgMjMuODI3NiAyNy4zNTkyIDIzLjY5MDYgMjcuMzU5MlpNMjQuOTk1NCAyNy4zMDI3VjI0LjMxNzNIMjUuNTc1OFYyNy4zMDI3SDI0Ljk5NTRaTTI2LjMxOTUgMjcuMzAyN1YyNS4wODczSDI2Ljg5NzZWMjcuMzAyN0gyNi4zMTk1Wk0yNi42MDg1IDI0LjgyNTRDMjYuNDk4NiAyNC44MjU0IDI2LjQxNzMgMjQuNzk5OCAyNi4zNjQ2IDI0Ljc0ODZDMjYuMzEzNSAyNC42OTU5IDI2LjI4NzkgMjQuNjIzNiAyNi4yODc5IDI0LjUzMThWMjQuNTIwNUMyNi4yODc5IDI0LjQyODcgMjYuMzEzNSAyNC4zNTY0IDI2LjM2NDYgMjQuMzAzN0MyNi40MTczIDI0LjI1MSAyNi40OTg2IDI0LjIyNDcgMjYuNjA4NSAyNC4yMjQ3QzI2LjcxNjkgMjQuMjI0NyAyNi43OTc1IDI0LjI1MSAyNi44NTAyIDI0LjMwMzdDMjYuOTAyOSAyNC4zNTY0IDI2LjkyOTIgMjQuNDI4NyAyNi45MjkyIDI0LjUyMDVWMjQuNTMxOEMyNi45MjkyIDI0LjYyNTIgMjYuOTAyOSAyNC42OTc0IDI2Ljg1MDIgMjQuNzQ4NkMyNi43OTc1IDI0Ljc5OTggMjYuNzE2OSAyNC44MjU0IDI2LjYwODUgMjQuODI1NFpNMjguNDA5NCAyNC4yNDczQzI4LjUyNjggMjQuMjQ3MyAyOC42MzUyIDI0LjI1NyAyOC43MzQ1IDI0LjI3NjZDMjguODMzOSAyNC4yOTYyIDI4LjkyMiAyNC4zMjAzIDI4Ljk5ODggMjQuMzQ4OUwyOS4wNTA3IDI0Ljc4MjVDMjguOTg2IDI0Ljc2MjkgMjguOTE4MiAyNC43NDcxIDI4Ljg0NzUgMjQuNzM1MUMyOC43NzgyIDI0LjcyMTUgMjguNzAxNCAyNC43MTQ3IDI4LjYxNzEgMjQuNzE0N0MyOC41MTYyIDI0LjcxNDcgMjguNDM2NSAyNC43MjYgMjguMzc3NyAyNC43NDg2QzI4LjMyMDUgMjQuNzcxMiAyOC4yNzk5IDI0LjgwMzYgMjguMjU1OCAyNC44NDU3QzI4LjIzMTcgMjQuODg3OSAyOC4yMTk3IDI0LjkzNzUgMjguMjE5NyAyNC45OTQ4VjI1LjAwMTVDMjguMjE5NyAyNS4wNDIyIDI4LjIyNTcgMjUuMDgwNiAyOC4yMzc3IDI1LjExNjdDMjguMjQ5OCAyNS4xNTI4IDI4LjI2NDEgMjUuMTg1MiAyOC4yODA2IDI1LjIxMzhMMjcuOTAzNSAyNS4yMjc0VjI1LjE2NjRDMjcuODM0MiAyNS4xMzMzIDI3Ljc3NTUgMjUuMDg0MyAyNy43MjczIDI1LjAxOTZDMjcuNjc5MiAyNC45NTQ5IDI3LjY1NTEgMjQuODc0MyAyNy42NTUxIDI0Ljc3OFYyNC43NjY3QzI3LjY1NTEgMjQuNjA3MSAyNy43MTYxIDI0LjQ4MDYgMjcuODM4IDI0LjM4NzNDMjcuOTYxNSAyNC4yOTM5IDI4LjE1MTkgMjQuMjQ3MyAyOC40MDk0IDI0LjI0NzNaTTI3LjczMTkgMjcuMzAyN1YyNS4zNDcxSDI4LjMwNzdWMjcuMzAyN0gyNy43MzE5Wk0yNy40MTEyIDI1LjY0MDZWMjUuMTgyMkwyNy45NzEyIDI1LjE4NjdMMjguMTk3MSAyNS4xODIySDI5LjA0NjJMMjguOTk2NSAyNS42NDA2SDI3LjQxMTJaTTI5Ljg1OTIgMjQuMjQ3M0MyOS45NzY2IDI0LjI0NzMgMzAuMDg1IDI0LjI1NyAzMC4xODQ0IDI0LjI3NjZDMzAuMjgzNyAyNC4yOTYyIDMwLjM3MTggMjQuMzIwMyAzMC40NDg2IDI0LjM0ODlMMzAuNTAwNSAyNC43ODI1QzMwLjQzNTggMjQuNzYyOSAzMC4zNjggMjQuNzQ3MSAzMC4yOTczIDI0LjczNTFDMzAuMjI4IDI0LjcyMTUgMzAuMTUxMiAyNC43MTQ3IDMwLjA2NjkgMjQuNzE0N0MyOS45NjYxIDI0LjcxNDcgMjkuODg2MyAyNC43MjYgMjkuODI3NiAyNC43NDg2QzI5Ljc3MDMgMjQuNzcxMiAyOS43Mjk3IDI0LjgwMzYgMjkuNzA1NiAyNC44NDU3QzI5LjY4MTUgMjQuODg3OSAyOS42Njk1IDI0LjkzNzUgMjkuNjY5NSAyNC45OTQ4VjI1LjAwMTVDMjkuNjY5NSAyNS4wNDIyIDI5LjY3NTUgMjUuMDgwNiAyOS42ODc1IDI1LjExNjdDMjkuNjk5NiAyNS4xNTI4IDI5LjcxMzkgMjUuMTg1MiAyOS43MzA1IDI1LjIxMzhMMjkuMzUzMyAyNS4yMjc0VjI1LjE2NjRDMjkuMjg0MSAyNS4xMzMzIDI5LjIyNTMgMjUuMDg0MyAyOS4xNzcyIDI1LjAxOTZDMjkuMTI5IDI0Ljk1NDkgMjkuMTA0OSAyNC44NzQzIDI5LjEwNDkgMjQuNzc4VjI0Ljc2NjdDMjkuMTA0OSAyNC42MDcxIDI5LjE2NTkgMjQuNDgwNiAyOS4yODc4IDI0LjM4NzNDMjkuNDExMyAyNC4yOTM5IDI5LjYwMTcgMjQuMjQ3MyAyOS44NTkyIDI0LjI0NzNaTTI5LjE4MTcgMjcuMzAyN1YyNS4zNDcxSDI5Ljc1NzZWMjcuMzAyN0gyOS4xODE3Wk0yOC44NjEgMjUuNjQwNlYyNS4xODIyTDI5LjQyMTEgMjUuMTg2N0wyOS42NDY5IDI1LjE4MjJIMzAuNDk2TDMwLjQ0NjMgMjUuNjQwNkgyOC44NjFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTUuOTg3NCAzMi4yODUyVjMxLjU1MDhIMTYuNzY5NEMxNi45OTM2IDMxLjU1MDggMTcuMTYxNyAzMS40OTAxIDE3LjI3MzkgMzEuMzY4N0MxNy4zODYgMzEuMjQ3MiAxNy40NDIgMzEuMDczNCAxNy40NDIgMzAuODQ3M1YzMC4xMjk4QzE3LjQ0MiAyOS45MDM3IDE3LjM4NiAyOS43MzA5IDE3LjI3MzkgMjkuNjExM0MxNy4xNjE3IDI5LjQ4OTkgMTYuOTkzNiAyOS40MjkxIDE2Ljc2OTQgMjkuNDI5MUgxNS45ODQ2VjI4LjcwMzJIMTYuODAwMkMxNy4zMTAzIDI4LjcwMzIgMTcuNjkxNSAyOC44MjY1IDE3Ljk0MzcgMjkuMDczMkMxOC4xOTc4IDI5LjMxOCAxOC4zMjQ5IDI5LjY3MDIgMTguMzI0OSAzMC4xMjk4VjMwLjg1MDFDMTguMzI0OSAzMS4zMTE3IDE4LjE5ODggMzEuNjY2NyAxNy45NDY1IDMxLjkxNTJDMTcuNjk0MyAzMi4xNjE4IDE3LjMxMjIgMzIuMjg1MiAxNi44MDAyIDMyLjI4NTJIMTUuOTg3NFpNMTUuMzczNiAzMi4yODUyVjI4LjcwMzJIMTYuMjM2OFYzMi4yODUySDE1LjM3MzZaTTE5LjA4NTIgMzIuMjg1MkwxOS4xOTczIDI4LjcwMzJIMjAuNDE5M0wyMS4wMTA3IDMwLjgzODlIMjEuMDQ3MkwyMS42Mzg1IDI4LjcwMzJIMjIuODYwNkwyMi45NzI3IDMyLjI4NTJIMjIuMTIzNEwyMi4wOTgyIDMxLjExMDhMMjIuMDczIDI5LjY4MTRIMjIuMDMwOUwyMS40MTcxIDMxLjg2NDdIMjAuNjM4TDIwLjAyNDIgMjkuNjgxNEgxOS45NzkzTDE5Ljk1NjkgMzEuMTEzNkwxOS45MzQ1IDMyLjI4NTJIMTkuMDg1MlpNMjQuNTIwOCAzMi4yODUyTDIzLjUzOTggMjguNzAzMkgyNC40NDc5TDI1LjE1MTQgMzEuNjMyMUgyNS4yMTU5TDI1LjkyMjIgMjguNzAzMkgyNi44Mjc1TDI1Ljg0OTMgMzIuMjg1MkgyNC41MjA4WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjAuOTc0MSAzLjU5OTYxTDIwLjU2NTggNC40MjQ4OUwxOS42NTQ1IDQuNTU4MTRMMjAuMzEyMiA1LjIwMjg5TDIwLjE1NzQgNi4xMTQxM0wyMC45NzQxIDUuNjg0M0wyMS43OTA4IDYuMTE0MTNMMjEuNjM2MSA1LjIwMjg5TDIyLjI5MzcgNC41NTgxNEwyMS4zODI1IDQuNDI0ODlMMjAuOTc0MSAzLjU5OTYxWiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjYuOTI3MyA0LjY3NDMyTDI2LjI2MSA1LjMxMDQ3TDI1LjM1ODQgNS4xMjEzNEwyNS43NTgxIDUuOTU1MjJMMjUuMzAyNSA2Ljc1NDcxTDI2LjIxMzcgNi42MzAwNkwyNi44MzcgNy4zMTM0OUwyNy4wMDAzIDYuNDA2NTRMMjcuODQyOCA2LjAyODI5TDI3LjAzMDQgNS41ODk4NkwyNi45MjczIDQuNjc0MzJaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zMi4xNTgzIDcuNzE3MjNMMzEuMzExNSA4LjA5MTE4TDMwLjUyOTIgNy42MDU0N0wzMC42MTk1IDguNTIxMDFMMjkuOTE0NiA5LjExODQ4TDMwLjgxNzIgOS4zMTYyTDMxLjE2NTQgMTAuMTY3M0wzMS42Mjk2IDkuMzcyMDhMMzIuNTQ5NCA5LjMwMzMxTDMxLjkzOTEgOC42MTU1OEwzMi4xNTgzIDcuNzE3MjNaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zNi4wMjY5IDEyLjM2ODVMMzUuMTA3MSAxMi40Mjg3TDM0LjUzNTQgMTEuNzA2NUwzNC4zMDc2IDEyLjYwMDZMMzMuNDQzNiAxMi45MTg3TDM0LjIyMTYgMTMuNDA4N0wzNC4yNjAzIDE0LjMzMjhMMzQuOTY5NSAxMy43NDRMMzUuODU1IDEzLjk5MzNMMzUuNTE1NCAxMy4xMzc5TDM2LjAyNjkgMTIuMzY4NVoiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTM4LjA3MjkgMTguMDYzNUwzNy4xOTE3IDE3LjgwNTZMMzYuODk5NCAxNi45Mjg3TDM2LjM3OTMgMTcuNjg5NUwzNS40NTk1IDE3LjY5MzhMMzYuMDIyNiAxOC40MjQ1TDM1Ljc0MzIgMTkuMzAxNEwzNi42MTE0IDE4Ljk5MTlMMzcuMzU5MyAxOS41MjkyTDM3LjMzMzUgMTguNjA5NEwzOC4wNzI5IDE4LjA2MzVaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zOC4wNTEzIDI0LjExMDdMMzcuMzA3NyAyMy41NjQ5TDM3LjMzNzggMjIuNjQ1TDM2LjU4NTUgMjMuMTgyM0wzNS43MTczIDIyLjg3MjhMMzYuMDAxIDIzLjc0OTdMMzUuNDMzNiAyNC40ODA0TDM2LjM1NzcgMjQuNDg0N0wzNi44Nzc4IDI1LjI0NTVMMzcuMTY1OCAyNC4zNjg2TDM4LjA1MTMgMjQuMTEwN1oiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTM1Ljk1ODEgMjkuNzg5NUwzNS40NDY1IDI5LjAyNDRMMzUuNzkwNCAyOC4xNjQ4TDM0LjkwMDcgMjguNDE4NEwzNC4xOTE0IDI3LjgyNTJMMzQuMTU3MSAyOC43NDkzTDMzLjM3NDggMjkuMjM5M0wzNC4yNDMgMjkuNTYxN0wzNC40NjY1IDMwLjQ1NThMMzUuMDM4MiAyOS43Mjk0TDM1Ljk1ODEgMjkuNzg5NVoiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTMyLjA1NTIgMzQuNDFMMzEuODM2IDMzLjUxNkwzMi40NDY0IDMyLjgyMzlMMzEuNTI2NiAzMi43NTUyTDMxLjA2MjMgMzEuOTZMMzAuNzE0MiAzMi44MTUzTDI5LjgxMTUgMzMuMDA4OEwzMC41MTY0IDMzLjYwNjJMMzAuNDI2MiAzNC41MjYxTDMxLjIwODUgMzQuMDQwNEwzMi4wNTUyIDM0LjQxWiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjYuODAyNyAzNy40MTQ2TDI2LjkwMTUgMzYuNDk5TDI3LjcxMzkgMzYuMDYwNkwyNi44NzU3IDM1LjY4MjNMMjYuNzA4MSAzNC43NzU0TDI2LjA4OTEgMzUuNDU4OEwyNS4xNzM2IDM1LjMzNDJMMjUuNjMzNSAzNi4xMzM3TDI1LjIzMzggMzYuOTYzMkwyNi4xMzY0IDM2Ljc3ODRMMjYuODAyNyAzNy40MTQ2WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjAuODQwNyAzOC40NDE5TDIxLjI0OTEgMzcuNjE2NkwyMi4xNjAzIDM3LjQ4MzNMMjEuNDk4NCAzNi44Mzg2TDIxLjY1NzQgMzUuOTMxNkwyMC44NDA3IDM2LjM2MTVMMjAuMDI0IDM1LjkzMTZMMjAuMTc4OCAzNi44Mzg2TDE5LjUxNjggMzcuNDgzM0wyMC40MzI0IDM3LjYxNjZMMjAuODQwNyAzOC40NDE5WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMTQuODgzNCAzNy4zNjcyTDE1LjU0OTcgMzYuNzMxMUwxNi40NTIzIDM2LjkyMDJMMTYuMDUyNiAzNi4wODYzTDE2LjUxMjUgMzUuMjg2OEwxNS41OTY5IDM1LjQxMTVMMTQuOTc4IDM0LjcyOEwxNC44MTAzIDM1LjYzNUwxMy45NzIyIDM2LjAxNzVMMTQuNzg0NiAzNi40NTE3TDE0Ljg4MzQgMzcuMzY3MloiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTkuNjU2NjggMzQuMzI0MUwxMC40OTkxIDMzLjk1NDRMMTEuMjg1NyAzNC40MzU4TDExLjE5MTIgMzMuNTIwM0wxMS44OTYxIDMyLjkyMjhMMTAuOTkzNSAzMi43Mjk0TDEwLjY0NTMgMzEuODc0TDEwLjE4MTEgMzIuNjY5Mkw5LjI2MTIzIDMyLjczOEw5Ljg3NTg5IDMzLjQyNTdMOS42NTY2OCAzNC4zMjQxWiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNNS43ODM2OSAyOS42NzMzTDYuNzAzNTMgMjkuNjEzMUw3LjI3NTIxIDMwLjMzOTZMNy41MDMwMiAyOS40NDU1TDguMzY2OTkgMjkuMTIzMUw3LjU4ODk5IDI4LjYzMzFMNy41NTQ2IDI3LjcwOUw2Ljg0NTM4IDI4LjI5NzlMNS45NTU2MiAyOC4wNDg2TDYuMjk1MTkgMjguOTA4Mkw1Ljc4MzY5IDI5LjY3MzNaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zLjczNzc5IDIzLjk3NzlMNC42MjMyNSAyNC4yNDAxTDQuOTExMjQgMjUuMTEyN0w1LjQzMTMzIDI0LjM1MTlMNi4zNTU0NyAyNC4zNDc2TDUuNzg4MSAyMy42MTY5TDYuMDcxNzkgMjIuNzRMNS4yMDM1MiAyMy4wNDk1TDQuNDUxMzIgMjIuNTEyMkw0LjQ4MTQgMjMuNDM2M0wzLjczNzc5IDIzLjk3NzlaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zLjc1OTI4IDE3LjkzMDJMNC41MDI4OSAxOC40NzYxTDQuNDc3MSAxOS4zOTU5TDUuMjI1MDEgMTguODU4Nkw2LjA5MzI3IDE5LjE2ODFMNS44MTM4OCAxOC4yOTEyTDYuMzc2OTYgMTcuNTYwNUw1LjQ1MjgyIDE3LjU1NjJMNC45MzcwMiAxNi43OTU0TDQuNjQ0NzMgMTcuNjcyM0wzLjc1OTI4IDE3LjkzMDJaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik01Ljg1MjU0IDEyLjI1MjJMNi4zNjQwNCAxMy4wMTczTDYuMDI0NDcgMTMuODc2OUw2LjkwOTkzIDEzLjYyNzZMNy42MTkxNSAxNC4yMTY1TDcuNjU3ODQgMTMuMjkyNEw4LjQzNTgzIDEyLjgwMjRMNy41NzE4NyAxMi40OEw3LjM0NDA2IDExLjU4NTlMNi43NzIzOCAxMi4zMTI0TDUuODUyNTQgMTIuMjUyMloiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTkuNzU5NyA3LjYzMTc4TDkuOTc4OTIgOC41MzAxM0w5LjM2NDI2IDkuMjE3ODZMMTAuMjg0MSA5LjI4NjYzTDEwLjc0ODMgMTAuMDgxOEwxMS4xMDA4IDkuMjI2NDZMMTEuOTk5MSA5LjAzMzAzTDExLjI5NDIgOC40MzU1NkwxMS4zODg4IDcuNTIwMDJMMTAuNjAyMiA4LjAwMTQzTDkuNzU5NyA3LjYzMTc4WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMTUuMDEyNSA0LjYyNjk1TDE0LjkwOTMgNS41NDI1TDE0LjA5NjkgNS45ODA5M0wxNC45Mzk0IDYuMzU5MThMMTUuMTAyNyA3LjI2NjEzTDE1LjcyNiA2LjU4MjY5TDE2LjYzNzIgNi43MDczNUwxNi4xODE2IDUuOTA3ODZMMTYuNTgxNCA1LjA3ODI4TDE1LjY3ODcgNS4yNjMxMUwxNS4wMTI1IDQuNjI2OTVaIiBmaWxsPSIjREM4NTQ4Ii8+CjxsaW5lIHgxPSI0OC45NzM4IiB5MT0iNC4zMjE3OCIgeDI9IjQ4Ljk3MzgiIHkyPSIzNy42Nzg0IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjAuNjExNjUzIi8+CjxwYXRoIGQ9Ik01Ny40MTEgMTguODkwNlYxNy40NjA2SDU5LjI5NkM1OS44ODk3IDE3LjQ2MDYgNjAuMzMzOCAxNy4zMDAzIDYwLjYyODUgMTYuOTc5NkM2MC45Mjc1IDE2LjY1NDYgNjEuMDc3IDE2LjE5MSA2MS4wNzcgMTUuNTg4NlYxMy44NjYxQzYxLjA3NyAxMy4yNjM4IDYwLjkyNzUgMTIuODAyMyA2MC42Mjg1IDEyLjQ4MTZDNjAuMzMzOCAxMi4xNjEgNTkuODg5NyAxMi4wMDA2IDU5LjI5NiAxMi4wMDA2SDU3LjQwNDVWMTAuNTgzNkg1OS4zNTQ1QzYwLjQ5ODUgMTAuNTgzNiA2MS4zNTg3IDEwLjg2NTMgNjEuOTM1IDExLjQyODZDNjIuNTExMyAxMS45OTIgNjIuNzk5NSAxMi44MDIzIDYyLjc5OTUgMTMuODU5NlYxNS41OTUxQzYyLjc5OTUgMTYuNjU2OCA2Mi41MTEzIDE3LjQ3MTUgNjEuOTM1IDE4LjAzOTFDNjEuMzYzIDE4LjYwNjggNjAuNTAyOCAxOC44OTA2IDU5LjM1NDUgMTguODkwNkg1Ny40MTFaTTU2LjIyMTUgMTguODkwNlYxMC41ODM2SDU3LjkwNVYxOC44OTA2SDU2LjIyMTVaTTY3LjA2NzggMTkuMDQ2NkM2NS45NzU4IDE5LjA0NjYgNjUuMTYzMyAxOC43OTUzIDY0LjYzMDMgMTguMjkyNkM2NC4wOTczIDE3Ljc5IDYzLjgzMDggMTcuMDYyIDYzLjgzMDggMTYuMTA4NlYxNS4yNzY2QzYzLjgzMDggMTQuMzMyIDY0LjA3OTkgMTMuNjA2MSA2NC41NzgzIDEzLjA5OTFDNjUuMDc2NiAxMi41OTIxIDY1LjgwMDMgMTIuMzM4NiA2Ni43NDkzIDEyLjMzODZDNjcuMzkwNiAxMi4zMzg2IDY3LjkyNTggMTIuNDUxMyA2OC4zNTQ4IDEyLjY3NjZDNjguNzgzOCAxMi45MDIgNjkuMTA0NCAxMy4yMjI2IDY5LjMxNjggMTMuNjM4NkM2OS41MzM0IDE0LjA1MDMgNjkuNjQxOCAxNC41NDQzIDY5LjY0MTggMTUuMTIwNlYxNS4zNDgxQzY5LjY0MTggMTUuNTA0MSA2OS42MzMxIDE1LjY2NDUgNjkuNjE1OCAxNS44MjkxQzY5LjYwMjggMTUuOTg5NSA2OS41ODMzIDE2LjE0MTEgNjkuNTU3MyAxNi4yODQxSDY4LjA0OTNDNjguMDYyMyAxNi4wNDU4IDY4LjA2ODggMTUuODIwNSA2OC4wNjg4IDE1LjYwODFDNjguMDczMSAxNS4zOTE1IDY4LjA3NTMgMTUuMTk2NSA2OC4wNzUzIDE1LjAyMzFDNjguMDc1MyAxNC43MjQxIDY4LjAyNzYgMTQuNDcwNiA2Ny45MzIzIDE0LjI2MjZDNjcuODM2OSAxNC4wNTAzIDY3LjY5MTggMTMuODkgNjcuNDk2OCAxMy43ODE2QzY3LjMwMTggMTMuNjczMyA2Ny4wNTI2IDEzLjYxOTEgNjYuNzQ5MyAxMy42MTkxQzY2LjMwMjkgMTMuNjE5MSA2NS45NzM2IDEzLjc0MjYgNjUuNzYxMyAxMy45ODk2QzY1LjU0ODkgMTQuMjM2NiA2NS40NDI4IDE0LjU4NzYgNjUuNDQyOCAxNS4wNDI2VjE1LjYzNDFMNjUuNDQ5MyAxNS44MjI2VjE2LjMyMzFDNjUuNDQ5MyAxNi41MjI1IDY1LjQ3OTYgMTYuNzA2NiA2NS41NDAzIDE2Ljg3NTZDNjUuNjA1MyAxNy4wNDQ2IDY1LjcxMTQgMTcuMTkyIDY1Ljg1ODggMTcuMzE3NkM2Ni4wMDYxIDE3LjQzOSA2Ni4yMDExIDE3LjUzNDMgNjYuNDQzOCAxNy42MDM2QzY2LjY5MDggMTcuNjczIDY2Ljk5ODQgMTcuNzA3NiA2Ny4zNjY4IDE3LjcwNzZDNjcuNzY1NCAxNy43MDc2IDY4LjE0NDYgMTcuNjY0MyA2OC41MDQzIDE3LjU3NzZDNjguODY4MyAxNy40ODY2IDY5LjIxMjggMTcuMzY1MyA2OS41Mzc4IDE3LjIxMzZMNjkuMzk0OCAxOC41MjY2QzY5LjEwNDQgMTguNjg3IDY4Ljc2MjEgMTguODEyNiA2OC4zNjc4IDE4LjkwMzZDNjcuOTc3OCAxOC45OTkgNjcuNTQ0NCAxOS4wNDY2IDY3LjA2NzggMTkuMDQ2NlpNNjQuNzE0OCAxNi4yODQxVjE1LjE3OTFINjkuMjE5M1YxNi4yODQxSDY0LjcxNDhaTTc0LjQ1MjUgMTkuMDQwMUM3NC4wNzExIDE5LjA0MDEgNzMuNzQ4MyAxOC45Nzk1IDczLjQ4NCAxOC44NTgxQzczLjIxOTYgMTguNzMyNSA3My4wMDUxIDE4LjU1NyA3Mi44NDA1IDE4LjMzMTZDNzIuNjgwMSAxOC4xMDYzIDcyLjU2NTMgMTcuODQyIDcyLjQ5NiAxNy41Mzg2SDcyLjAyMTVMNzIuNDQ0IDE2LjI2NDZDNzIuNDUyNiAxNi41NjggNzIuNTEzMyAxNi44MjM2IDcyLjYyNiAxNy4wMzE2QzcyLjc0MyAxNy4yMzk2IDcyLjkwMzMgMTcuMzk1NiA3My4xMDcgMTcuNDk5NkM3My4zMTUgMTcuNjAzNiA3My41NTc2IDE3LjY1NTYgNzMuODM1IDE3LjY1NTZDNzQuMjU1MyAxNy42NTU2IDc0LjU3NiAxNy41MzQzIDc0Ljc5NyAxNy4yOTE2Qzc1LjAxOCAxNy4wNDQ2IDc1LjEyODUgMTYuNjgwNiA3NS4xMjg1IDE2LjE5OTZWMTUuMTUzMUM3NS4xMjg1IDE0LjY3NjUgNzUuMDIwMSAxNC4zMTY4IDc0LjgwMzUgMTQuMDc0MUM3NC41ODY4IDEzLjgzMTUgNzQuMjY2MSAxMy43MTAxIDczLjg0MTUgMTMuNzEwMUM3My41OTg4IDEzLjcxMDEgNzMuMzggMTMuNzU3OCA3My4xODUgMTMuODUzMUM3Mi45OSAxMy45NDQxIDcyLjgyNzUgMTQuMDY3NiA3Mi42OTc1IDE0LjIyMzZDNzIuNTY3NSAxNC4zNzk2IDcyLjQ3NDMgMTQuNTU5NSA3Mi40MTggMTQuNzYzMUw3Mi4wMjggMTMuODY2MUg3Mi40ODk1QzcyLjU1ODggMTMuNTg4OCA3Mi42NjkzIDEzLjMzNzUgNzIuODIxIDEzLjExMjFDNzIuOTc3IDEyLjg4MjUgNzMuMTkxNSAxMi43MDI2IDczLjQ2NDUgMTIuNTcyNkM3My43NDE4IDEyLjQzODMgNzQuMDkwNiAxMi4zNzExIDc0LjUxMSAxMi4zNzExQzc1LjI2MDYgMTIuMzcxMSA3NS44MzA1IDEyLjYxMzggNzYuMjIwNSAxMy4wOTkxQzc2LjYxMDUgMTMuNTgwMSA3Ni44MDU1IDE0LjI5MyA3Ni44MDU1IDE1LjIzNzZWMTYuMTIxNkM3Ni44MDU1IDE3LjA3NSA3Ni42MDgzIDE3LjgwMDggNzYuMjE0IDE4LjI5OTFDNzUuODI0IDE4Ljc5MzEgNzUuMjM2OCAxOS4wNDAxIDc0LjQ1MjUgMTkuMDQwMVpNNzAuODEyNSAyMS4xMjY2VjEyLjUxNDFINzIuNDc2NUw3Mi40MTE1IDE0LjEzMjZMNzIuNDQ0IDE0LjQyNTFWMTYuOTc5Nkw3Mi40MjQ1IDE3LjI3ODZMNzIuNDcgMTkuMDI3MVYyMS4xMjY2SDcwLjgxMjVaTTgxLjcwMjcgMTguODkwNkw4MS43NjEyIDE3LjMzMDZMODEuNzE1NyAxNy4xODc2VjE1LjE5MjFMODEuNzA5MiAxNC45MDYxQzgxLjcwOTIgMTQuNDkwMSA4MS41OTQ0IDE0LjE4NDYgODEuMzY0NyAxMy45ODk2QzgxLjEzOTQgMTMuNzk0NiA4MC43Njg5IDEzLjY5NzEgODAuMjUzMiAxMy42OTcxQzc5LjgxNTUgMTMuNjk3MSA3OS40MDM5IDEzLjc1NTYgNzkuMDE4MiAxMy44NzI2Qzc4LjYzNjkgMTMuOTg1MyA3OC4yODM3IDE0LjExNzUgNzcuOTU4NyAxNC4yNjkxTDc4LjEwMTcgMTIuOTQzMUM3OC4yOTI0IDEyLjg0MzUgNzguNTA5IDEyLjc1MDMgNzguNzUxNyAxMi42NjM2Qzc4Ljk5ODcgMTIuNTcyNiA3OS4yNzM5IDEyLjQ5OSA3OS41NzcyIDEyLjQ0MjZDNzkuODgwNSAxMi4zODYzIDgwLjIwNzcgMTIuMzU4MSA4MC41NTg3IDEyLjM1ODFDODEuMDc4NyAxMi4zNTgxIDgxLjUxODUgMTIuNDIxIDgxLjg3ODIgMTIuNTQ2NkM4Mi4yMzc5IDEyLjY2OCA4Mi41MjM5IDEyLjg0MzUgODIuNzM2MiAxMy4wNzMxQzgyLjk1MjkgMTMuMzAyOCA4My4xMDg5IDEzLjU3OCA4My4yMDQyIDEzLjg5ODZDODMuMjk5NSAxNC4yMTUgODMuMzQ3MiAxNC41NjYgODMuMzQ3MiAxNC45NTE2VjE4Ljg5MDZIODEuNzAyN1pNNzkuNjE2MiAxOS4wNDAxQzc4Ljk4MzUgMTkuMDQwMSA3OC41MDA0IDE4Ljg4MiA3OC4xNjY3IDE4LjU2NTZDNzcuODM3NCAxOC4yNDkzIDc3LjY3MjcgMTcuNzk4NiA3Ny42NzI3IDE3LjIxMzZWMTcuMDMxNkM3Ny42NzI3IDE2LjQxMiA3Ny44NjM0IDE1Ljk1NDggNzguMjQ0NyAxNS42NjAxQzc4LjYyNiAxNS4zNjExIDc5LjIzMDUgMTUuMTU1MyA4MC4wNTgyIDE1LjA0MjZMODEuODY1MiAxNC43OTU2TDgxLjk2MjcgMTUuODY4MUw4MC4yOTg3IDE2LjEwODZDNzkuOTM0NyAxNi4xNTYzIDc5LjY3NDcgMTYuMjQzIDc5LjUxODcgMTYuMzY4NkM3OS4zNjcgMTYuNDk0MyA3OS4yOTEyIDE2LjY3ODUgNzkuMjkxMiAxNi45MjExVjE2Ljk4NjFDNzkuMjkxMiAxNy4yMjQ1IDc5LjM2NDkgMTcuNDEwOCA3OS41MTIyIDE3LjU0NTFDNzkuNjYzOSAxNy42NzUxIDc5LjkgMTcuNzQwMSA4MC4yMjA3IDE3Ljc0MDFDODAuNTA2NyAxNy43NDAxIDgwLjc1MTUgMTcuNjk0NiA4MC45NTUyIDE3LjYwMzZDODEuMTU4OSAxNy41MTI2IDgxLjMyNTcgMTcuMzkzNSA4MS40NTU3IDE3LjI0NjFDODEuNTkgMTcuMDk0NSA4MS42ODU0IDE2LjkyNTUgODEuNzQxNyAxNi43MzkxTDgxLjk3NTcgMTcuNTY0Nkg4MS42ODk3QzgxLjYyMDQgMTcuODM3NiA4MS41MDc3IDE4LjA4NjggODEuMzUxNyAxOC4zMTIxQzgxLjIgMTguNTMzMSA4MC45ODU1IDE4LjcxMDggODAuNzA4MiAxOC44NDUxQzgwLjQzMDkgMTguOTc1MSA4MC4wNjY5IDE5LjA0MDEgNzkuNjE2MiAxOS4wNDAxWk04Ni4zMzIzIDE1LjE4NTZMODUuOTA5OCAxNC4wNzQxSDg2LjMxMjhDODYuNDI5OCAxMy41NTg1IDg2LjY0MjIgMTMuMTUzMyA4Ni45NDk4IDEyLjg1ODZDODcuMjU3NSAxMi41NjQgODcuNjg0MyAxMi40MTY2IDg4LjIzMDMgMTIuNDE2NkM4OC4zNDMgMTIuNDE2NiA4OC40NDQ4IDEyLjQyNTMgODguNTM1OCAxMi40NDI2Qzg4LjYyNjggMTIuNDU1NiA4OC43MDkyIDEyLjQ3MyA4OC43ODI4IDEyLjQ5NDZMODguODczOCAxNC4xNTg2Qzg4Ljc3ODUgMTQuMTI4MyA4OC42NjggMTQuMTA2NiA4OC41NDIzIDE0LjA5MzZDODguNDE2NyAxNC4wNzYzIDg4LjI4NDUgMTQuMDY3NiA4OC4xNDU4IDE0LjA2NzZDODcuNzAzOCAxNC4wNjc2IDg3LjMyNjggMTQuMTY1MSA4Ny4wMTQ4IDE0LjM2MDFDODYuNzA3MiAxNC41NTUxIDg2LjQ3OTcgMTQuODMwMyA4Ni4zMzIzIDE1LjE4NTZaTTg0LjcxMzggMTguODkwNlYxMi41MTQxSDg2LjI5OThMODYuMjI4MyAxNC40NzA2TDg2LjM3NzggMTQuNTI5MVYxOC44OTA2SDg0LjcxMzhaTTkyLjQ3NzggMTkuMDI3MUM5MS45MzYxIDE5LjAyNzEgOTEuNTAyOCAxOC45NDcgOTEuMTc3OCAxOC43ODY2QzkwLjg1NzEgMTguNjIyIDkwLjYyNTMgMTguMzc1IDkwLjQ4MjMgMTguMDQ1NkM5MC4zMzkzIDE3LjcxNjMgOTAuMjY3OCAxNy4zMTExIDkwLjI2NzggMTYuODMwMVYxMy4xMTIxSDkxLjkxODhWMTYuNTgzMUM5MS45MTg4IDE2LjkyOTggOTEuOTk2OCAxNy4xODU1IDkyLjE1MjggMTcuMzUwMUM5Mi4zMTMxIDE3LjUxMDUgOTIuNTkyNiAxNy41OTA2IDkyLjk5MTMgMTcuNTkwNkM5My4yMjUzIDE3LjU5MDYgOTMuNDUwNiAxNy41NjY4IDkzLjY2NzMgMTcuNTE5MUM5My44ODQgMTcuNDY3MSA5NC4wODMzIDE3LjQgOTQuMjY1MyAxNy4zMTc2TDk0LjEyMjMgMTguNzA4NkM5My45MDU2IDE4LjgwODMgOTMuNjU2NSAxOC44ODYzIDkzLjM3NDggMTguOTQyNkM5My4wOTc1IDE4Ljk5OSA5Mi43OTg1IDE5LjAyNzEgOTIuNDc3OCAxOS4wMjcxWk04OS4zNDQ4IDEzLjg3OTFWMTIuNTY2MUg5NC4yMDY4TDk0LjA2MzggMTMuODc5MUg4OS4zNDQ4Wk05MC4yODczIDEyLjY4OTZMOTAuMjgwOCAxMC45OTk2TDkxLjkzODMgMTAuODMwNkw5MS44NzMzIDEyLjY4OTZIOTAuMjg3M1pNMTAzLjA4NCAxOC44OTA2VjE1LjAxNjZDMTAzLjA4NCAxNC43NTY2IDEwMy4wNTIgMTQuNTMzNSAxMDIuOTg3IDE0LjM0NzFDMTAyLjkyMiAxNC4xNTY1IDEwMi44MTMgMTQuMDA5MSAxMDIuNjYyIDEzLjkwNTFDMTAyLjUxNCAxMy44MDExIDEwMi4zMTMgMTMuNzQ5MSAxMDIuMDU3IDEzLjc0OTFDMTAxLjgyMyAxMy43NDkxIDEwMS42MTkgMTMuNzk0NiAxMDEuNDQ2IDEzLjg4NTZDMTAxLjI3MyAxMy45NzY2IDEwMS4xMzIgMTQuMTAwMSAxMDEuMDI0IDE0LjI1NjFDMTAwLjkxNSAxNC40MDc4IDEwMC44MzUgMTQuNTgxMSAxMDAuNzgzIDE0Ljc3NjFMMTAwLjYwMSAxMy44NjYxSDEwMC43N0MxMDAuODM5IDEzLjU5MzEgMTAwLjk1MiAxMy4zNDQgMTAxLjEwOCAxMy4xMTg2QzEwMS4yNjQgMTIuODg5IDEwMS40NzYgMTIuNzA3IDEwMS43NDUgMTIuNTcyNkMxMDIuMDE4IDEyLjQzODMgMTAyLjM2MyAxMi4zNzExIDEwMi43NzkgMTIuMzcxMUMxMDMuMjQyIDEyLjM3MTEgMTAzLjYxNyAxMi40NjIxIDEwMy45MDMgMTIuNjQ0MUMxMDQuMTkzIDEyLjgyMTggMTA0LjQwOCAxMy4wOTA1IDEwNC41NDcgMTMuNDUwMUMxMDQuNjg1IDEzLjgwNTUgMTA0Ljc1NSAxNC4yNDc1IDEwNC43NTUgMTQuNzc2MVYxOC44OTA2SDEwMy4wODRaTTk1LjIyNTYgMTguODkwNlYxMi41MTQxSDk2Ljg4OTZMOTYuODI0NiAxNC4xMzI2TDk2Ljg4OTYgMTQuMjA0MVYxOC44OTA2SDk1LjIyNTZaTTk5LjE1ODEgMTguODkwNlYxNS4wMTY2Qzk5LjE1ODEgMTQuNzU2NiA5OS4xMjU2IDE0LjUzMzUgOTkuMDYwNiAxNC4zNDcxQzk4Ljk5NTYgMTQuMTU2NSA5OC44ODcyIDE0LjAwOTEgOTguNzM1NiAxMy45MDUxQzk4LjU4ODIgMTMuODAxMSA5OC4zODY3IDEzLjc0OTEgOTguMTMxMSAxMy43NDkxQzk3Ljg5MjcgMTMuNzQ5MSA5Ny42ODY5IDEzLjc5NDYgOTcuNTEzNiAxMy44ODU2Qzk3LjM0NDYgMTMuOTc2NiA5Ny4yMDM3IDE0LjEwMDEgOTcuMDkxMSAxNC4yNTYxQzk2Ljk4MjcgMTQuNDA3OCA5Ni45MDI2IDE0LjU4MTEgOTYuODUwNiAxNC43NzYxTDk2LjU5MDYgMTMuODY2MUg5Ni45MDI2Qzk2Ljk2NzYgMTMuNTg0NSA5Ny4wNzU5IDEzLjMzMSA5Ny4yMjc2IDEzLjEwNTZDOTcuMzgzNiAxMi44ODAzIDk3LjU5MzcgMTIuNzAyNiA5Ny44NTgxIDEyLjU3MjZDOTguMTIyNCAxMi40MzgzIDk4LjQ1MTcgMTIuMzcxMSA5OC44NDYxIDEyLjM3MTFDOTkuNDM5NyAxMi4zNzExIDk5Ljg4NjEgMTIuNTIyOCAxMDAuMTg1IDEyLjgyNjFDMTAwLjQ4OCAxMy4xMjk1IDEwMC42NzkgMTMuNTcxNSAxMDAuNzU3IDE0LjE1MjFDMTAwLjc3NCAxNC4yMzg4IDEwMC43OSAxNC4zNDA2IDEwMC44MDMgMTQuNDU3NkMxMDAuODE2IDE0LjU3MDMgMTAwLjgyMiAxNC42NzY1IDEwMC44MjIgMTQuNzc2MVYxOC44OTA2SDk5LjE1ODFaTTEwOS4xNCAxOS4wNDY2QzEwOC4wNDggMTkuMDQ2NiAxMDcuMjM2IDE4Ljc5NTMgMTA2LjcwMyAxOC4yOTI2QzEwNi4xNyAxNy43OSAxMDUuOTAzIDE3LjA2MiAxMDUuOTAzIDE2LjEwODZWMTUuMjc2NkMxMDUuOTAzIDE0LjMzMiAxMDYuMTUyIDEzLjYwNjEgMTA2LjY1MSAxMy4wOTkxQzEwNy4xNDkgMTIuNTkyMSAxMDcuODczIDEyLjMzODYgMTA4LjgyMiAxMi4zMzg2QzEwOS40NjMgMTIuMzM4NiAxMDkuOTk4IDEyLjQ1MTMgMTEwLjQyNyAxMi42NzY2QzExMC44NTYgMTIuOTAyIDExMS4xNzcgMTMuMjIyNiAxMTEuMzg5IDEzLjYzODZDMTExLjYwNiAxNC4wNTAzIDExMS43MTQgMTQuNTQ0MyAxMTEuNzE0IDE1LjEyMDZWMTUuMzQ4MUMxMTEuNzE0IDE1LjUwNDEgMTExLjcwNSAxNS42NjQ1IDExMS42ODggMTUuODI5MUMxMTEuNjc1IDE1Ljk4OTUgMTExLjY1NiAxNi4xNDExIDExMS42MyAxNi4yODQxSDExMC4xMjJDMTEwLjEzNSAxNi4wNDU4IDExMC4xNDEgMTUuODIwNSAxMTAuMTQxIDE1LjYwODFDMTEwLjE0NSAxNS4zOTE1IDExMC4xNDggMTUuMTk2NSAxMTAuMTQ4IDE1LjAyMzFDMTEwLjE0OCAxNC43MjQxIDExMC4xIDE0LjQ3MDYgMTEwLjAwNSAxNC4yNjI2QzEwOS45MDkgMTQuMDUwMyAxMDkuNzY0IDEzLjg5IDEwOS41NjkgMTMuNzgxNkMxMDkuMzc0IDEzLjY3MzMgMTA5LjEyNSAxMy42MTkxIDEwOC44MjIgMTMuNjE5MUMxMDguMzc1IDEzLjYxOTEgMTA4LjA0NiAxMy43NDI2IDEwNy44MzQgMTMuOTg5NkMxMDcuNjIxIDE0LjIzNjYgMTA3LjUxNSAxNC41ODc2IDEwNy41MTUgMTUuMDQyNlYxNS42MzQxTDEwNy41MjIgMTUuODIyNlYxNi4zMjMxQzEwNy41MjIgMTYuNTIyNSAxMDcuNTUyIDE2LjcwNjYgMTA3LjYxMyAxNi44NzU2QzEwNy42NzggMTcuMDQ0NiAxMDcuNzg0IDE3LjE5MiAxMDcuOTMxIDE3LjMxNzZDMTA4LjA3OCAxNy40MzkgMTA4LjI3MyAxNy41MzQzIDEwOC41MTYgMTcuNjAzNkMxMDguNzYzIDE3LjY3MyAxMDkuMDcxIDE3LjcwNzYgMTA5LjQzOSAxNy43MDc2QzEwOS44MzggMTcuNzA3NiAxMTAuMjE3IDE3LjY2NDMgMTEwLjU3NyAxNy41Nzc2QzExMC45NDEgMTcuNDg2NiAxMTEuMjg1IDE3LjM2NTMgMTExLjYxIDE3LjIxMzZMMTExLjQ2NyAxOC41MjY2QzExMS4xNzcgMTguNjg3IDExMC44MzQgMTguODEyNiAxMTAuNDQgMTguOTAzNkMxMTAuMDUgMTguOTk5IDEwOS42MTcgMTkuMDQ2NiAxMDkuMTQgMTkuMDQ2NlpNMTA2Ljc4NyAxNi4yODQxVjE1LjE3OTFIMTExLjI5MlYxNi4yODQxSDEwNi43ODdaTTExNy4wMzggMTguODkwNlYxNS4wNjIxQzExNy4wMzggMTQuNzkzNSAxMTcuMDAxIDE0LjU2MTYgMTE2LjkyOCAxNC4zNjY2QzExNi44NTggMTQuMTcxNiAxMTYuNzQxIDE0LjAyIDExNi41NzcgMTMuOTExNkMxMTYuNDEyIDEzLjgwMzMgMTE2LjE4NyAxMy43NDkxIDExNS45MDEgMTMuNzQ5MUMxMTUuNjQ5IDEzLjc0OTEgMTE1LjQyOCAxMy43OTQ2IDExNS4yMzggMTMuODg1NkMxMTUuMDUxIDEzLjk3NjYgMTE0Ljg5OCAxNC4xMDAxIDExNC43NzYgMTQuMjU2MUMxMTQuNjU5IDE0LjQwNzggMTE0LjU3IDE0LjU4MTEgMTE0LjUxIDE0Ljc3NjFMMTE0LjI1IDEzLjg2NjFIMTE0LjU2MkMxMTQuNjMxIDEzLjU4NDUgMTE0Ljc0NiAxMy4zMzEgMTE0LjkwNiAxMy4xMDU2QzExNS4wNzEgMTIuODgwMyAxMTUuMjkyIDEyLjcwMjYgMTE1LjU2OSAxMi41NzI2QzExNS44NTEgMTIuNDM4MyAxMTYuMjAyIDEyLjM3MTEgMTE2LjYyMiAxMi4zNzExQzExNy4xMTIgMTIuMzcxMSAxMTcuNTA4IDEyLjQ2NDMgMTE3LjgxMiAxMi42NTA2QzExOC4xMTUgMTIuODMyNiAxMTguMzM4IDEzLjEwNTYgMTE4LjQ4MSAxMy40Njk2QzExOC42MjkgMTMuODMzNiAxMTguNzAyIDE0LjI4NDMgMTE4LjcwMiAxNC44MjE2VjE4Ljg5MDZIMTE3LjAzOFpNMTEyLjg4NSAxOC44OTA2VjEyLjUxNDFIMTE0LjU0OUwxMTQuNDg0IDE0LjA2NzZMMTE0LjU0OSAxNC4yMDQxVjE4Ljg5MDZIMTEyLjg4NVpNMTIyLjU0IDE5LjAyNzFDMTIxLjk5OSAxOS4wMjcxIDEyMS41NjUgMTguOTQ3IDEyMS4yNCAxOC43ODY2QzEyMC45MiAxOC42MjIgMTIwLjY4OCAxOC4zNzUgMTIwLjU0NSAxOC4wNDU2QzEyMC40MDIgMTcuNzE2MyAxMjAuMzMgMTcuMzExMSAxMjAuMzMgMTYuODMwMVYxMy4xMTIxSDEyMS45ODFWMTYuNTgzMUMxMjEuOTgxIDE2LjkyOTggMTIyLjA1OSAxNy4xODU1IDEyMi4yMTUgMTcuMzUwMUMxMjIuMzc2IDE3LjUxMDUgMTIyLjY1NSAxNy41OTA2IDEyMy4wNTQgMTcuNTkwNkMxMjMuMjg4IDE3LjU5MDYgMTIzLjUxMyAxNy41NjY4IDEyMy43MyAxNy41MTkxQzEyMy45NDYgMTcuNDY3MSAxMjQuMTQ2IDE3LjQgMTI0LjMyOCAxNy4zMTc2TDEyNC4xODUgMTguNzA4NkMxMjMuOTY4IDE4LjgwODMgMTIzLjcxOSAxOC44ODYzIDEyMy40MzcgMTguOTQyNkMxMjMuMTYgMTguOTk5IDEyMi44NjEgMTkuMDI3MSAxMjIuNTQgMTkuMDI3MVpNMTE5LjQwNyAxMy44NzkxVjEyLjU2NjFIMTI0LjI2OUwxMjQuMTI2IDEzLjg3OTFIMTE5LjQwN1pNMTIwLjM1IDEyLjY4OTZMMTIwLjM0MyAxMC45OTk2TDEyMi4wMDEgMTAuODMwNkwxMjEuOTM2IDEyLjY4OTZIMTIwLjM1Wk0xMzAuNzIzIDE5LjA2NjFDMTI5LjczNSAxOS4wNjYxIDEyOC45ODUgMTguODEyNiAxMjguNDc0IDE4LjMwNTZDMTI3Ljk2NyAxNy43OTg2IDEyNy43MTQgMTcuMDc3MSAxMjcuNzE0IDE2LjE0MTFWMTUuMjc2NkMxMjcuNzE0IDE0LjMzNjMgMTI3Ljk2NyAxMy42MTI2IDEyOC40NzQgMTMuMTA1NkMxMjguOTg1IDEyLjU5NDMgMTI5LjczNSAxMi4zMzg2IDEzMC43MjMgMTIuMzM4NkMxMzEuNzA3IDEyLjMzODYgMTMyLjQ1MiAxMi41OTQzIDEzMi45NTkgMTMuMTA1NkMxMzMuNDY2IDEzLjYxMjYgMTMzLjcyIDE0LjMzNjMgMTMzLjcyIDE1LjI3NjZWMTYuMTQxMUMxMzMuNzIgMTcuMDc3MSAxMzMuNDY2IDE3Ljc5ODYgMTMyLjk1OSAxOC4zMDU2QzEzMi40NTYgMTguODEyNiAxMzEuNzExIDE5LjA2NjEgMTMwLjcyMyAxOS4wNjYxWk0xMzAuNzIzIDE3LjczMzZDMTMxLjE1NiAxNy43MzM2IDEzMS40ODYgMTcuNjAzNiAxMzEuNzExIDE3LjM0MzZDMTMxLjk0MSAxNy4wODM2IDEzMi4wNTYgMTYuNzExIDEzMi4wNTYgMTYuMjI1NlYxNS4xOTIxQzEzMi4wNTYgMTQuNjk4MSAxMzEuOTQxIDE0LjMyMTEgMTMxLjcxMSAxNC4wNjExQzEzMS40ODYgMTMuNzk2OCAxMzEuMTU2IDEzLjY2NDYgMTMwLjcyMyAxMy42NjQ2QzEzMC4yODUgMTMuNjY0NiAxMjkuOTUyIDEzLjc5NjggMTI5LjcyMiAxNC4wNjExQzEyOS40OTcgMTQuMzIxMSAxMjkuMzg0IDE0LjY5ODEgMTI5LjM4NCAxNS4xOTIxVjE2LjIyNTZDMTI5LjM4NCAxNi43MTEgMTI5LjQ5NyAxNy4wODM2IDEyOS43MjIgMTcuMzQzNkMxMjkuOTUyIDE3LjYwMzYgMTMwLjI4NSAxNy43MzM2IDEzMC43MjMgMTcuNzMzNlpNMTM3LjE1NCAxMC4wOTYxQzEzNy40OTIgMTAuMDk2MSAxMzcuODA0IDEwLjEyNDMgMTM4LjA5IDEwLjE4MDZDMTM4LjM3NiAxMC4yMzcgMTM4LjYyOSAxMC4zMDYzIDEzOC44NSAxMC4zODg2TDEzOSAxMS42MzY2QzEzOC44MTMgMTEuNTgwMyAxMzguNjE4IDExLjUzNDggMTM4LjQxNSAxMS41MDAxQzEzOC4yMTUgMTEuNDYxMSAxMzcuOTk0IDExLjQ0MTYgMTM3Ljc1MiAxMS40NDE2QzEzNy40NjEgMTEuNDQxNiAxMzcuMjMyIDExLjQ3NDEgMTM3LjA2MyAxMS41MzkxQzEzNi44OTggMTEuNjA0MSAxMzYuNzgxIDExLjY5NzMgMTM2LjcxMiAxMS44MTg2QzEzNi42NDIgMTEuOTQgMTM2LjYwOCAxMi4wODMgMTM2LjYwOCAxMi4yNDc2VjEyLjI2NzFDMTM2LjYwOCAxMi4zODQxIDEzNi42MjUgMTIuNDk0NiAxMzYuNjYgMTIuNTk4NkMxMzYuNjk0IDEyLjcwMjYgMTM2LjczNSAxMi43OTU4IDEzNi43ODMgMTIuODc4MUwxMzUuNjk4IDEyLjkxNzFWMTIuNzQxNkMxMzUuNDk4IDEyLjY0NjMgMTM1LjMyOSAxMi41MDU1IDEzNS4xOTEgMTIuMzE5MUMxMzUuMDUyIDEyLjEzMjggMTM0Ljk4MyAxMS45MDEgMTM0Ljk4MyAxMS42MjM2VjExLjU5MTFDMTM0Ljk4MyAxMS4xMzE4IDEzNS4xNTggMTAuNzY3OCAxMzUuNTA5IDEwLjQ5OTFDMTM1Ljg2NCAxMC4yMzA1IDEzNi40MTMgMTAuMDk2MSAxMzcuMTU0IDEwLjA5NjFaTTEzNS4yMDQgMTguODkwNlYxMy4yNjE2SDEzNi44NjFWMTguODkwNkgxMzUuMjA0Wk0xMzQuMjgxIDE0LjEwNjZWMTIuNzg3MUwxMzUuODkzIDEyLjgwMDFMMTM2LjU0MyAxMi43ODcxSDEzOC45ODdMMTM4Ljg0NCAxNC4xMDY2SDEzNC4yODFaTTU2LjExNzUgMzEuODkwNkw1Ni4zOTA1IDIzLjU4MzZINTguODQ3NUw2MC40MDc1IDI4Ljg2ODFINjAuNDkyTDYyLjA0NTUgMjMuNTgzNkg2NC40OTZMNjQuNzc1NSAzMS44OTA2SDYzLjEyNDVMNjMuMDQ2NSAyOC45MDA2TDYyLjk3NSAyNS40NDkxSDYyLjg3MUw2MS4yMzk1IDMwLjkyMjFINTkuNjUzNUw1OC4wMTU1IDI1LjQ0OTFINTcuOTExNUw1Ny44NCAyOC45MDcxTDU3Ljc2ODUgMzEuODkwNkg1Ni4xMTc1Wk02OC45NjA0IDMyLjA2NjFDNjcuOTcyNCAzMi4wNjYxIDY3LjIyMjcgMzEuODEyNiA2Ni43MTE0IDMxLjMwNTZDNjYuMjA0NCAzMC43OTg2IDY1Ljk1MDkgMzAuMDc3MSA2NS45NTA5IDI5LjE0MTFWMjguMjc2NkM2NS45NTA5IDI3LjMzNjMgNjYuMjA0NCAyNi42MTI2IDY2LjcxMTQgMjYuMTA1NkM2Ny4yMjI3IDI1LjU5NDMgNjcuOTcyNCAyNS4zMzg2IDY4Ljk2MDQgMjUuMzM4NkM2OS45NDQgMjUuMzM4NiA3MC42ODk0IDI1LjU5NDMgNzEuMTk2NCAyNi4xMDU2QzcxLjcwMzQgMjYuNjEyNiA3MS45NTY5IDI3LjMzNjMgNzEuOTU2OSAyOC4yNzY2VjI5LjE0MTFDNzEuOTU2OSAzMC4wNzcxIDcxLjcwMzQgMzAuNzk4NiA3MS4xOTY0IDMxLjMwNTZDNzAuNjkzNyAzMS44MTI2IDY5Ljk0ODQgMzIuMDY2MSA2OC45NjA0IDMyLjA2NjFaTTY4Ljk2MDQgMzAuNzMzNkM2OS4zOTM3IDMwLjczMzYgNjkuNzIzIDMwLjYwMzYgNjkuOTQ4NCAzMC4zNDM2QzcwLjE3OCAzMC4wODM2IDcwLjI5MjkgMjkuNzExIDcwLjI5MjkgMjkuMjI1NlYyOC4xOTIxQzcwLjI5MjkgMjcuNjk4MSA3MC4xNzggMjcuMzIxMSA2OS45NDg0IDI3LjA2MTFDNjkuNzIzIDI2Ljc5NjggNjkuMzkzNyAyNi42NjQ2IDY4Ljk2MDQgMjYuNjY0NkM2OC41MjI3IDI2LjY2NDYgNjguMTg5IDI2Ljc5NjggNjcuOTU5NCAyNy4wNjExQzY3LjczNCAyNy4zMjExIDY3LjYyMTQgMjcuNjk4MSA2Ny42MjE0IDI4LjE5MjFWMjkuMjI1NkM2Ny42MjE0IDI5LjcxMSA2Ny43MzQgMzAuMDgzNiA2Ny45NTk0IDMwLjM0MzZDNjguMTg5IDMwLjYwMzYgNjguNTIyNyAzMC43MzM2IDY4Ljk2MDQgMzAuNzMzNlpNNzUuNjA1NyAzMi4wMjcxQzc1LjA2NCAzMi4wMjcxIDc0LjYzMDcgMzEuOTQ3IDc0LjMwNTcgMzEuNzg2NkM3My45ODUgMzEuNjIyIDczLjc1MzIgMzEuMzc1IDczLjYxMDIgMzEuMDQ1NkM3My40NjcyIDMwLjcxNjMgNzMuMzk1NyAzMC4zMTExIDczLjM5NTcgMjkuODMwMVYyNi4xMTIxSDc1LjA0NjdWMjkuNTgzMUM3NS4wNDY3IDI5LjkyOTggNzUuMTI0NyAzMC4xODU1IDc1LjI4MDcgMzAuMzUwMUM3NS40NDEgMzAuNTEwNSA3NS43MjA1IDMwLjU5MDYgNzYuMTE5MiAzMC41OTA2Qzc2LjM1MzIgMzAuNTkwNiA3Ni41Nzg1IDMwLjU2NjggNzYuNzk1MiAzMC41MTkxQzc3LjAxMTkgMzAuNDY3MSA3Ny4yMTEyIDMwLjQgNzcuMzkzMiAzMC4zMTc2TDc3LjI1MDIgMzEuNzA4NkM3Ny4wMzM1IDMxLjgwODMgNzYuNzg0NCAzMS44ODYzIDc2LjUwMjcgMzEuOTQyNkM3Ni4yMjU0IDMxLjk5OSA3NS45MjY0IDMyLjAyNzEgNzUuNjA1NyAzMi4wMjcxWk03Mi40NzI3IDI2Ljg3OTFWMjUuNTY2MUg3Ny4zMzQ3TDc3LjE5MTcgMjYuODc5MUg3Mi40NzI3Wk03My40MTUyIDI1LjY4OTZMNzMuNDA4NyAyMy45OTk2TDc1LjA2NjIgMjMuODMwNkw3NS4wMDEyIDI1LjY4OTZINzMuNDE1MlpNODEuMDA4MiAzMi4wNjYxQzgwLjAyMDIgMzIuMDY2MSA3OS4yNzA2IDMxLjgxMjYgNzguNzU5MiAzMS4zMDU2Qzc4LjI1MjIgMzAuNzk4NiA3Ny45OTg3IDMwLjA3NzEgNzcuOTk4NyAyOS4xNDExVjI4LjI3NjZDNzcuOTk4NyAyNy4zMzYzIDc4LjI1MjIgMjYuNjEyNiA3OC43NTkyIDI2LjEwNTZDNzkuMjcwNiAyNS41OTQzIDgwLjAyMDIgMjUuMzM4NiA4MS4wMDgyIDI1LjMzODZDODEuOTkxOSAyNS4zMzg2IDgyLjczNzIgMjUuNTk0MyA4My4yNDQyIDI2LjEwNTZDODMuNzUxMiAyNi42MTI2IDg0LjAwNDcgMjcuMzM2MyA4NC4wMDQ3IDI4LjI3NjZWMjkuMTQxMUM4NC4wMDQ3IDMwLjA3NzEgODMuNzUxMiAzMC43OTg2IDgzLjI0NDIgMzEuMzA1NkM4Mi43NDE2IDMxLjgxMjYgODEuOTk2MiAzMi4wNjYxIDgxLjAwODIgMzIuMDY2MVpNODEuMDA4MiAzMC43MzM2QzgxLjQ0MTYgMzAuNzMzNiA4MS43NzA5IDMwLjYwMzYgODEuOTk2MiAzMC4zNDM2QzgyLjIyNTkgMzAuMDgzNiA4Mi4zNDA3IDI5LjcxMSA4Mi4zNDA3IDI5LjIyNTZWMjguMTkyMUM4Mi4zNDA3IDI3LjY5ODEgODIuMjI1OSAyNy4zMjExIDgxLjk5NjIgMjcuMDYxMUM4MS43NzA5IDI2Ljc5NjggODEuNDQxNiAyNi42NjQ2IDgxLjAwODIgMjYuNjY0NkM4MC41NzA2IDI2LjY2NDYgODAuMjM2OSAyNi43OTY4IDgwLjAwNzIgMjcuMDYxMUM3OS43ODE5IDI3LjMyMTEgNzkuNjY5MiAyNy42OTgxIDc5LjY2OTIgMjguMTkyMVYyOS4yMjU2Qzc5LjY2OTIgMjkuNzExIDc5Ljc4MTkgMzAuMDgzNiA4MC4wMDcyIDMwLjM0MzZDODAuMjM2OSAzMC42MDM2IDgwLjU3MDYgMzAuNzMzNiA4MS4wMDgyIDMwLjczMzZaTTg2Ljg0MDIgMjguMTg1Nkw4Ni40MTc3IDI3LjA3NDFIODYuODIwN0M4Ni45Mzc3IDI2LjU1ODUgODcuMTUgMjYuMTUzMyA4Ny40NTc3IDI1Ljg1ODZDODcuNzY1MyAyNS41NjQgODguMTkyMiAyNS40MTY2IDg4LjczODIgMjUuNDE2NkM4OC44NTA4IDI1LjQxNjYgODguOTUyNyAyNS40MjUzIDg5LjA0MzcgMjUuNDQyNkM4OS4xMzQ3IDI1LjQ1NTYgODkuMjE3IDI1LjQ3MyA4OS4yOTA3IDI1LjQ5NDZMODkuMzgxNyAyNy4xNTg2Qzg5LjI4NjMgMjcuMTI4MyA4OS4xNzU4IDI3LjEwNjYgODkuMDUwMiAyNy4wOTM2Qzg4LjkyNDUgMjcuMDc2MyA4OC43OTIzIDI3LjA2NzYgODguNjUzNyAyNy4wNjc2Qzg4LjIxMTcgMjcuMDY3NiA4Ny44MzQ3IDI3LjE2NTEgODcuNTIyNyAyNy4zNjAxQzg3LjIxNSAyNy41NTUxIDg2Ljk4NzUgMjcuODMwMyA4Ni44NDAyIDI4LjE4NTZaTTg1LjIyMTcgMzEuODkwNlYyNS41MTQxSDg2LjgwNzdMODYuNzM2MiAyNy40NzA2TDg2Ljg4NTcgMjcuNTI5MVYzMS44OTA2SDg1LjIyMTdaTTk0Ljc2NzIgMzEuODkwNkw5Mi40MDc3IDIzLjU4MzZIOTQuMTc1N0w5NS45OTU3IDMwLjYxNjZIOTYuMTQ1Mkw5Ny45NjUyIDIzLjU4MzZIOTkuNzMzMkw5Ny4zODAyIDMxLjg5MDZIOTQuNzY3MlpNMTAzLjA0NiAzMi4wNDY2QzEwMS45NTQgMzIuMDQ2NiAxMDEuMTQyIDMxLjc5NTMgMTAwLjYwOSAzMS4yOTI2QzEwMC4wNzYgMzAuNzkgOTkuODA5MyAzMC4wNjIgOTkuODA5MyAyOS4xMDg2VjI4LjI3NjZDOTkuODA5MyAyNy4zMzIgMTAwLjA1OCAyNi42MDYxIDEwMC41NTcgMjYuMDk5MUMxMDEuMDU1IDI1LjU5MjEgMTAxLjc3OSAyNS4zMzg2IDEwMi43MjggMjUuMzM4NkMxMDMuMzY5IDI1LjMzODYgMTAzLjkwNCAyNS40NTEzIDEwNC4zMzMgMjUuNjc2NkMxMDQuNzYyIDI1LjkwMiAxMDUuMDgzIDI2LjIyMjYgMTA1LjI5NSAyNi42Mzg2QzEwNS41MTIgMjcuMDUwMyAxMDUuNjIgMjcuNTQ0MyAxMDUuNjIgMjguMTIwNlYyOC4zNDgxQzEwNS42MiAyOC41MDQxIDEwNS42MTIgMjguNjY0NSAxMDUuNTk0IDI4LjgyOTFDMTA1LjU4MSAyOC45ODk1IDEwNS41NjIgMjkuMTQxMSAxMDUuNTM2IDI5LjI4NDFIMTA0LjAyOEMxMDQuMDQxIDI5LjA0NTggMTA0LjA0NyAyOC44MjA1IDEwNC4wNDcgMjguNjA4MUMxMDQuMDUyIDI4LjM5MTUgMTA0LjA1NCAyOC4xOTY1IDEwNC4wNTQgMjguMDIzMUMxMDQuMDU0IDI3LjcyNDEgMTA0LjAwNiAyNy40NzA2IDEwMy45MTEgMjcuMjYyNkMxMDMuODE1IDI3LjA1MDMgMTAzLjY3IDI2Ljg5IDEwMy40NzUgMjYuNzgxNkMxMDMuMjggMjYuNjczMyAxMDMuMDMxIDI2LjYxOTEgMTAyLjcyOCAyNi42MTkxQzEwMi4yODEgMjYuNjE5MSAxMDEuOTUyIDI2Ljc0MjYgMTAxLjc0IDI2Ljk4OTZDMTAxLjUyNyAyNy4yMzY2IDEwMS40MjEgMjcuNTg3NiAxMDEuNDIxIDI4LjA0MjZWMjguNjM0MUwxMDEuNDI4IDI4LjgyMjZWMjkuMzIzMUMxMDEuNDI4IDI5LjUyMjUgMTAxLjQ1OCAyOS43MDY2IDEwMS41MTkgMjkuODc1NkMxMDEuNTg0IDMwLjA0NDYgMTAxLjY5IDMwLjE5MiAxMDEuODM3IDMwLjMxNzZDMTAxLjk4NSAzMC40MzkgMTAyLjE4IDMwLjUzNDMgMTAyLjQyMiAzMC42MDM2QzEwMi42NjkgMzAuNjczIDEwMi45NzcgMzAuNzA3NiAxMDMuMzQ1IDMwLjcwNzZDMTAzLjc0NCAzMC43MDc2IDEwNC4xMjMgMzAuNjY0MyAxMDQuNDgzIDMwLjU3NzZDMTA0Ljg0NyAzMC40ODY2IDEwNS4xOTEgMzAuMzY1MyAxMDUuNTE2IDMwLjIxMzZMMTA1LjM3MyAzMS41MjY2QzEwNS4wODMgMzEuNjg3IDEwNC43NDEgMzEuODEyNiAxMDQuMzQ2IDMxLjkwMzZDMTAzLjk1NiAzMS45OTkgMTAzLjUyMyAzMi4wNDY2IDEwMy4wNDYgMzIuMDQ2NlpNMTAwLjY5MyAyOS4yODQxVjI4LjE3OTFIMTA1LjE5OFYyOS4yODQxSDEwMC42OTNaTTExMC45NDQgMzEuODkwNlYyOC4wNjIxQzExMC45NDQgMjcuNzkzNSAxMTAuOTA4IDI3LjU2MTYgMTEwLjgzNCAyNy4zNjY2QzExMC43NjUgMjcuMTcxNiAxMTAuNjQ4IDI3LjAyIDExMC40ODMgMjYuOTExNkMxMTAuMzE4IDI2LjgwMzMgMTEwLjA5MyAyNi43NDkxIDEwOS44MDcgMjYuNzQ5MUMxMDkuNTU2IDI2Ljc0OTEgMTA5LjMzNyAyNi43OTQ2IDEwOS4xNSAyNi44ODU2QzEwOC45NjQgMjYuOTc2NiAxMDguODEgMjcuMTAwMSAxMDguNjg5IDI3LjI1NjFDMTA4LjU3MiAyNy40MDc4IDEwOC40ODUgMjcuNTgxMSAxMDguNDI5IDI3Ljc3NjFMMTA4LjA5MSAyNi44NjYxSDEwOC40ODdDMTA4LjU2MSAyNi41ODQ1IDEwOC42NzggMjYuMzMxIDEwOC44MzggMjYuMTA1NkMxMDguOTk5IDI1Ljg4MDMgMTA5LjIxOCAyNS43MDI2IDEwOS40OTUgMjUuNTcyNkMxMDkuNzcyIDI1LjQzODMgMTEwLjExNyAyNS4zNzExIDExMC41MjggMjUuMzcxMUMxMTEuMDE4IDI1LjM3MTEgMTExLjQxNSAyNS40NjQzIDExMS43MTggMjUuNjUwNkMxMTIuMDIxIDI1LjgzMjYgMTEyLjI0NCAyNi4xMDU2IDExMi4zODcgMjYuNDY5NkMxMTIuNTM1IDI2LjgzMzYgMTEyLjYwOCAyNy4yODQzIDExMi42MDggMjcuODIxNlYzMS44OTA2SDExMC45NDRaTTEwNi43OTEgMzEuODkwNlYyMy4yOTc2SDEwOC40NDhWMjUuMjYwNkwxMDguNDE2IDI3LjI0MzFMMTA4LjQ1NSAyNy4zNjY2VjMxLjg5MDZIMTA2Ljc5MVpNMTE0LjAxNSAzMS44OTA2VjI1LjUxNDFIMTE1LjY3OVYzMS44OTA2SDExNC4wMTVaTTExNC44NDcgMjQuNzYwMUMxMTQuNTMxIDI0Ljc2MDEgMTE0LjI5NyAyNC42ODY1IDExNC4xNDUgMjQuNTM5MUMxMTMuOTk4IDI0LjM4NzUgMTEzLjkyNCAyNC4xNzk1IDExMy45MjQgMjMuOTE1MVYyMy44ODI2QzExMy45MjQgMjMuNjE4MyAxMTMuOTk4IDIzLjQxMDMgMTE0LjE0NSAyMy4yNTg2QzExNC4yOTcgMjMuMTA3IDExNC41MzEgMjMuMDMxMSAxMTQuODQ3IDIzLjAzMTFDMTE1LjE1OSAyMy4wMzExIDExNS4zOTEgMjMuMTA3IDExNS41NDIgMjMuMjU4NkMxMTUuNjk0IDIzLjQxMDMgMTE1Ljc3IDIzLjYxODMgMTE1Ljc3IDIzLjg4MjZWMjMuOTE1MUMxMTUuNzcgMjQuMTgzOCAxMTUuNjk0IDI0LjM5MTggMTE1LjU0MiAyNC41MzkxQzExNS4zOTEgMjQuNjg2NSAxMTUuMTU5IDI0Ljc2MDEgMTE0Ljg0NyAyNC43NjAxWk0xMTkuNzk2IDMyLjA1MzFDMTE4LjgxMyAzMi4wNTMxIDExOC4wODIgMzEuNzkzMSAxMTcuNjA2IDMxLjI3MzFDMTE3LjEzMyAzMC43NTMxIDExNi44OTcgMzAuMDIzIDExNi44OTcgMjkuMDgyNlYyOC4zMDI2QzExNi44OTcgMjcuMzY2NiAxMTcuMTM2IDI2LjY0MDggMTE3LjYxMiAyNi4xMjUxQzExOC4wODkgMjUuNjA5NSAxMTguODE3IDI1LjM1MTYgMTE5Ljc5NiAyNS4zNTE2QzEyMC4wNTIgMjUuMzUxNiAxMjAuMjkgMjUuMzczMyAxMjAuNTExIDI1LjQxNjZDMTIwLjczNyAyNS40NTU2IDEyMC45NCAyNS41MDk4IDEyMS4xMjIgMjUuNTc5MUMxMjEuMzA5IDI1LjY0ODUgMTIxLjQ3MSAyNS43MjIxIDEyMS42MSAyNS44MDAxTDEyMS43NDYgMjcuMTk3NkMxMjEuNTM0IDI3LjA2MzMgMTIxLjI5NiAyNi45NTA2IDEyMS4wMzEgMjYuODU5NkMxMjAuNzcxIDI2Ljc2ODYgMTIwLjQ3IDI2LjcyMzEgMTIwLjEyOCAyNi43MjMxQzExOS41OSAyNi43MjMxIDExOS4xOTYgMjYuODYxOCAxMTguOTQ1IDI3LjEzOTFDMTE4LjY5OCAyNy40MTIxIDExOC41NzQgMjcuODEwOCAxMTguNTc0IDI4LjMzNTFWMjkuMDI0MUMxMTguNTc0IDI5LjU0NDEgMTE4LjcwMiAyOS45NDUgMTE4Ljk1OCAzMC4yMjY2QzExOS4yMTggMzAuNTA4MyAxMTkuNjE2IDMwLjY0OTEgMTIwLjE1NCAzMC42NDkxQzEyMC40OTYgMzAuNjQ5MSAxMjAuNzk5IDMwLjYwNTggMTIxLjA2NCAzMC41MTkxQzEyMS4zMjggMzAuNDI4MSAxMjEuNTc1IDMwLjMxNzYgMTIxLjgwNSAzMC4xODc2TDEyMS42NjggMzEuNTkxNkMxMjEuNDU2IDMxLjcxMyAxMjEuMTg5IDMxLjgxOTEgMTIwLjg2OSAzMS45MTAxQzEyMC41NDggMzIuMDA1NSAxMjAuMTkxIDMyLjA1MzEgMTE5Ljc5NiAzMi4wNTMxWk0xMjIuOTAyIDMxLjg5MDZWMjMuMjk3NkgxMjQuNTcyVjMxLjg5MDZIMTIyLjkwMlpNMTI5LjAzNCAzMi4wNDY2QzEyNy45NDIgMzIuMDQ2NiAxMjcuMTI5IDMxLjc5NTMgMTI2LjU5NiAzMS4yOTI2QzEyNi4wNjMgMzAuNzkgMTI1Ljc5NyAzMC4wNjIgMTI1Ljc5NyAyOS4xMDg2VjI4LjI3NjZDMTI1Ljc5NyAyNy4zMzIgMTI2LjA0NiAyNi42MDYxIDEyNi41NDQgMjYuMDk5MUMxMjcuMDQyIDI1LjU5MjEgMTI3Ljc2NiAyNS4zMzg2IDEyOC43MTUgMjUuMzM4NkMxMjkuMzU2IDI1LjMzODYgMTI5Ljg5MiAyNS40NTEzIDEzMC4zMjEgMjUuNjc2NkMxMzAuNzUgMjUuOTAyIDEzMS4wNyAyNi4yMjI2IDEzMS4yODMgMjYuNjM4NkMxMzEuNDk5IDI3LjA1MDMgMTMxLjYwOCAyNy41NDQzIDEzMS42MDggMjguMTIwNlYyOC4zNDgxQzEzMS42MDggMjguNTA0MSAxMzEuNTk5IDI4LjY2NDUgMTMxLjU4MiAyOC44MjkxQzEzMS41NjkgMjguOTg5NSAxMzEuNTQ5IDI5LjE0MTEgMTMxLjUyMyAyOS4yODQxSDEzMC4wMTVDMTMwLjAyOCAyOS4wNDU4IDEzMC4wMzUgMjguODIwNSAxMzAuMDM1IDI4LjYwODFDMTMwLjAzOSAyOC4zOTE1IDEzMC4wNDEgMjguMTk2NSAxMzAuMDQxIDI4LjAyMzFDMTMwLjA0MSAyNy43MjQxIDEyOS45OTMgMjcuNDcwNiAxMjkuODk4IDI3LjI2MjZDMTI5LjgwMyAyNy4wNTAzIDEyOS42NTggMjYuODkgMTI5LjQ2MyAyNi43ODE2QzEyOS4yNjggMjYuNjczMyAxMjkuMDE4IDI2LjYxOTEgMTI4LjcxNSAyNi42MTkxQzEyOC4yNjkgMjYuNjE5MSAxMjcuOTM5IDI2Ljc0MjYgMTI3LjcyNyAyNi45ODk2QzEyNy41MTUgMjcuMjM2NiAxMjcuNDA5IDI3LjU4NzYgMTI3LjQwOSAyOC4wNDI2VjI4LjYzNDFMMTI3LjQxNSAyOC44MjI2VjI5LjMyMzFDMTI3LjQxNSAyOS41MjI1IDEyNy40NDUgMjkuNzA2NiAxMjcuNTA2IDI5Ljg3NTZDMTI3LjU3MSAzMC4wNDQ2IDEyNy42NzcgMzAuMTkyIDEyNy44MjUgMzAuMzE3NkMxMjcuOTcyIDMwLjQzOSAxMjguMTY3IDMwLjUzNDMgMTI4LjQxIDMwLjYwMzZDMTI4LjY1NyAzMC42NzMgMTI4Ljk2NCAzMC43MDc2IDEyOS4zMzMgMzAuNzA3NkMxMjkuNzMxIDMwLjcwNzYgMTMwLjExIDMwLjY2NDMgMTMwLjQ3IDMwLjU3NzZDMTMwLjgzNCAzMC40ODY2IDEzMS4xNzkgMzAuMzY1MyAxMzEuNTA0IDMwLjIxMzZMMTMxLjM2MSAzMS41MjY2QzEzMS4wNyAzMS42ODcgMTMwLjcyOCAzMS44MTI2IDEzMC4zMzQgMzEuOTAzNkMxMjkuOTQ0IDMxLjk5OSAxMjkuNTEgMzIuMDQ2NiAxMjkuMDM0IDMyLjA0NjZaTTEyNi42ODEgMjkuMjg0MVYyOC4xNzkxSDEzMS4xODVWMjkuMjg0MUgxMjYuNjgxWk0xMzQuOTk0IDMyLjA0NjZDMTM0LjQ4MyAzMi4wNDY2IDEzNC4wMjggMzEuOTk2OCAxMzMuNjI5IDMxLjg5NzFDMTMzLjIzNSAzMS44MDE4IDEzMi44OTkgMzEuNjkzNSAxMzIuNjIxIDMxLjU3MjFMMTMyLjQ3MiAzMC4xMjkxQzEzMi44MDEgMzAuMjgwOCAxMzMuMTYzIDMwLjQxMyAxMzMuNTU3IDMwLjUyNTZDMTMzLjk1NiAzMC42MzgzIDEzNC4zOTQgMzAuNjk0NiAxMzQuODcgMzAuNjk0NkMxMzUuMjg2IDMwLjY5NDYgMTM1LjU4OCAzMC42NDI2IDEzNS43NzQgMzAuNTM4NkMxMzUuOTYgMzAuNDMwMyAxMzYuMDUzIDMwLjI3IDEzNi4wNTMgMzAuMDU3NlYzMC4wMTg2QzEzNi4wNTMgMjkuODc1NiAxMzYuMDEgMjkuNzU4NiAxMzUuOTIzIDI5LjY2NzZDMTM1Ljg0MSAyOS41NzY2IDEzNS42OTQgMjkuNDk0MyAxMzUuNDgxIDI5LjQyMDZDMTM1LjI2OSAyOS4zNDI2IDEzNC45NyAyOS4yNjAzIDEzNC41ODQgMjkuMTczNkMxMzQuMDUxIDI5LjA0OCAxMzMuNjI5IDI4LjkwMjggMTMzLjMxNyAyOC43MzgxQzEzMy4wMDkgMjguNTY5MSAxMzIuNzg4IDI4LjM2MzMgMTMyLjY1NCAyOC4xMjA2QzEzMi41MiAyNy44NzM2IDEzMi40NTIgMjcuNTc5IDEzMi40NTIgMjcuMjM2NlYyNy4xNzgxQzEzMi40NTIgMjYuNTc1OCAxMzIuNjY3IDI2LjEyMyAxMzMuMDk2IDI1LjgxOTZDMTMzLjUyNSAyNS41MTIgMTM0LjE2IDI1LjM1ODEgMTM1IDI1LjM1ODFDMTM1LjQ5OSAyNS4zNTgxIDEzNS45MzkgMjUuNDA4IDEzNi4zMiAyNS41MDc2QzEzNi43MDYgMjUuNjAzIDEzNy4wMjYgMjUuNzE3OCAxMzcuMjgyIDI1Ljg1MjFMMTM3LjQzMSAyNy4xNzgxQzEzNy4xMjggMjcuMDM1MSAxMzYuNzg4IDI2LjkxNiAxMzYuNDExIDI2LjgyMDZDMTM2LjAzNCAyNi43MjEgMTM1LjYyOSAyNi42NzExIDEzNS4xOTUgMjYuNjcxMUMxMzQuOTE0IDI2LjY3MTEgMTM0LjY5MSAyNi42OTUgMTM0LjUyNiAyNi43NDI2QzEzNC4zNjYgMjYuNzg2IDEzNC4yNTEgMjYuODQ4OCAxMzQuMTgxIDI2LjkzMTFDMTM0LjExMiAyNy4wMTM1IDEzNC4wNzcgMjcuMTEzMSAxMzQuMDc3IDI3LjIzMDFWMjcuMjYyNkMxMzQuMDc3IDI3LjM5MjYgMTM0LjExNCAyNy41MDUzIDEzNC4xODggMjcuNjAwNkMxMzQuMjY2IDI3LjY5NiAxMzQuNDA3IDI3Ljc4MjYgMTM0LjYxIDI3Ljg2MDZDMTM0LjgxNCAyNy45MzQzIDEzNS4xIDI4LjAxNDUgMTM1LjQ2OCAyOC4xMDExQzEzNi4wMDYgMjguMjEzOCAxMzYuNDM3IDI4LjM0NiAxMzYuNzYyIDI4LjQ5NzZDMTM3LjA4NyAyOC42NDkzIDEzNy4zMjMgMjguODQ0MyAxMzcuNDcgMjkuMDgyNkMxMzcuNjE4IDI5LjMxNjYgMTM3LjY5MSAyOS42MjQzIDEzNy42OTEgMzAuMDA1NlYzMC4wODM2QzEzNy42OTEgMzAuNzQyMyAxMzcuNDY4IDMxLjIzNDEgMTM3LjAyMiAzMS41NTkxQzEzNi41NzYgMzEuODg0MSAxMzUuOSAzMi4wNDY2IDEzNC45OTQgMzIuMDQ2NloiIGZpbGw9IiNGNkY2RjYiLz4KPC9zdmc+ClhAybDn2tK8ED02UtdSJzxV+NF3tMuGQLcvndUGObrYgXP9y2NBNV6vVsNmkNt4zDnadCx8uyAr93vIuAsBRMiUkGpuYW1lU3BhY2VzoXFvcmcuaXNvLjE4MDEzLjUuMZLYGFhIpGhkaWdlc3RJRAFmcmFuZG9tUF2BsFh1JOboDp6yK4r12XVxZWxlbWVudElkZW50aWZpZXJjc2V4bGVsZW1lbnRWYWx1ZWEx2BhZEtekaGRpZ2VzdElED2ZyYW5kb21QVHu6WoNegd/NwwUmKLfU6nFlbGVtZW50SWRlbnRpZmllcmhwb3J0cmFpdGxlbGVtZW50VmFsdWVZEomJUE5HDQoaCgAAAA1JSERSAAAAbwAAAI8IAwAAAMNfddAAAAAEZ0FNQQAAsY8L/GEFAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAEhQTFRFr66puYx1roJronZgDAgGHBMPlmxWqqqlo6KenJuWlJKO0aWRPSsiLR8YimJNfVdDx5mCTDcsXkIziomFbks517KgfHp1ZGFctcDraQAAEcpJREFUaN6MmolioyAQhlGjqAENaOX933Tn4lJjlu62TXN8/HMxKGrU/IX/TkPBF31TSvFPTf8uQ1//wO+/DhV5iBsrWKRFRgEuoPRXnanyWGemKskqqRPYqNPULu+V7ydCqVHr+GzEqErmqIQ1FrDCnCxRqVqvUhGbkLrkapUoZ6RiRkZeWap8dZZeSbs8jIYpuZX/tK4Uqmj76P7SCypGUWVTfZ7AjfvSr+M5PsWId7j4/NfwJKAu31hMNesbL/mQdMR3ZOvUdiwEZ3k5mlN2FZOuE0IVVrtm0kVS+mMdPzo7M06YM328U5byTkkuqOrZU4KrXCJOgVoSb2tAkecx+GP2ydROvBxeOQNOwZoccuO1OkBTaIObaeAPnexYZ8w5lnKkqtsap5J/igyil87BO2Nc2OZtm6cxl4iiYhazvZaeK690gfyLcTUdPgTnjO1NmOcjwDgmEjudBaqKlou9OieVSsKyOPHZdhxbMH3/6mFYZ+gX6w8UOl4sekp+XQXxdQUqtSFsnLbgAUEDgfydyCByVuTXM03frVoXXlEzBTaC+RzqiRACItkaY82xbZRQ03bMhTWr+q10VeQuCZ5tibBgX3GwKOMMIUGesyYc2zQHZ10I/hhjvJ6reGm1U/XKjkNDOluxQFPfO++NtUiGRwBy8RHSp5sGoK5Eqq74yczjhIZ8kb8i1Bq/G+v21aNMa2QSFof4NMxfWdmD6rTIJFz/KgZ+tjX7DtR1dc552/NfgQ3q+t44/OX4KVBdchRyIFBMJiBaDwYY04E5PUgEgSlyEAQSgQyJejzok/ZH1w6GBA9EimmARoPPWpd1X3fjLDhxd9amUAW1RIThjjA96FNF2iV1U5BPioaUB71bh2NZyXYGklKiyZJCLD9g3N7NQT/48LrEIC6Sku8oIuCn2QcAYj6izKTfsA/pVWH7GqVF35fHFNhQkt0UJug+lIBpBybFPAFInyMJnvWi0G5S5e546pwNgDOVKPzHCQY8AAERotMSJct3MS0hsvymHni6FqiPhOsxyTi1LEtFGegib0wfgb0YlHOCSlAYH3Kw9B78trk+JZuFD6byUYQNfShEq+XCDdGSUpPtifM5nvKv6upGl13CwxpTpb3l4i3J3iee+I/+6OYf/kuPNxvVGZovBMWJ96LoZG0QpS8r1ds7rqQ03KSeciJZcwwxJrGUeFzRgWcqHpiOp4Qg5uHL992J/2htHL/leyVvipMnnpOSYezJosmfOJ2e5e306rhI2vAQMZl9pPXbcKz0xOOZp+CP6Rl5qMcbEwOGBc8/7Qm5Z3hNiOlGNdhiZ2Z7G92WlvieX4Z/lVJu+9QJGP+TplVIJZhLIlUWm2oVPYpA/lTrOD0xPn2OFxS4/bToGOu0jdkQkyINicHUXRgjRcjIEiEKX8C/Xx6KB6Prk53o3UbqJhZHjyWLoyLGhJRp1i6roFgbUiJs6seYTUw9Ix9jCOJ3v8NY13VZIOx5bcXX4SSoUBvmScmmGLDH9itApz5Xeyfr+c5jXYZh6Lp2GJbduwQgyXsGRn34/WndZd72KpKPLCmygNa1TdN8Pk3TAXIFDCmj5+Frx1ixlLIpbcz4VGNwxOyLDQJ0EGDBZUFhwOLRNC0ilwW6C5rJgI9WcmwMGA66WT02FpjtsZMmFzq0Ygestm0I96ZByLaFJ8jEPFBzaVCcd3i2ptIhTY1y3C/kspYtGXGRCVSU2vIogbHsfe9jpOeM+xJZzlacP+Mi7+/9B4OJH4YKD8SuVIjychnGZ4GTOWxubVleW/H++IuJzCSVJHAYdlca1IYf8Xm4zRQN0M7ymkIeqftLEt+FRORhqphc148fvBAqHppzaGv3/dXEtwhsEs/nxfk379hcsTVZOcNv5bFhRWHiDWvBg733L3sek0+TS/qI18TwLHhR3yfrW3FJih/hfvD0HKZQdJQ7lbBa3zvSBFfrI176BHc8Zh+u7vNx5rH3LgZ956xI+hbgxU0TfsLD+iDXK8btKPeW3/UV4RIDhmrcXvDsMf6qZwDMWwLmfdGXc6LQh7zsPj/rZ2vimEw2KAToMqRsoOryV0doaVDkrT5Xs36bnve5PEKuMHf2/LsRyPWb9aUt2suO4/Ouk8cc4hsMxEuh70pjiTFAmZeri9H6tzr0oFgUdl1LzPdvvFIg8VafLqA8Lw4ZOUmJAffRSts+4FKNYd5AAmMx0//hPbw4JVcKDCy2y1DyboFRoOhbvfC+NROXS9TjzG/BxV30fVOYCzZ4uRWefTKnrvZlYlDOeUfd30DT5r4BlRb5zqIgy6mZWXCBh7ELb/rqOXW6yKy56wXegu2mp8HN2NJF4htgw4qdoaOGEF62ljx/e2eivGafvTjSpSVH73bcXUt7CRWgZVcCjvoV/rPMh95B4e33+dv+9nSJGVOQnE7zpW4WhVJTjQ1bx8UG3YV/ozlwPwgvRSDy/LofT2WzvtY7eJwkz1ZiFMoHW3YfWu56yZowl0GaJSSuwrOgfHnY3uZbgzQOsM2r9xRu1O+u2D972UIM1PkCn+aDlkQsvxZNAr4wsFNdlP5+Aaa+pTTAXujV78KDmLAG+9+eNhPoQQhNkIfedbivpfafNjPIw6nC3LrbeNE3t4f0BrPuLTXy6BZjw2HNy5hAzhwgRCFcBhI9gyWM98EYBNIb4K34a3t73fzqPBgtTNVaST7oEPZjoosdW0cVvMFlCXjLsA8qYGQeAwXLwh1Fb/Ct89dCdpE4wTQN8nD7BRm4DvMGJjomyPkG/hGvGzDbP1MIe4BYAhq8muaHFy2XZbzXdwkWvGkG7yJ9UFHgI1betHB9wcrFPOCmxgzcRoWvRZ5F3qCv++fLnaZ412IbFmcBQ7UT6z4uS/jFlRIXBfwZ9w6YDUN8LaQC8rp0K0/fp19J3WAnYJgnuz3ZlIG8RvThI17Xo/aGkhL9DbW0re52fNEX73POQ7fi8tDxZza5padPxTatoS1haufpWeFh0aNwuVyHr2/G52MG09AtPvLyeLPDRB8yaLF9pxeAPSEnQV4L4RKviuu7dE/384g6QmBjvScefaSMpuCRPumx8RW0PC3MG7p85/F0KT7bMckDB0JGYHvdNtWulh3WfBKvei7xOnhjvp9YXT4ujanT4YZxgoCDfmJggW/prElCm/TRc7JPYkdyeC7d0s1fTgZcsoGZYNDFsUHzxojcd+ZFnKz2WG0hPboxSqvujVc3nVPIjJQRC1SohQXyvp2MBuHf4goPvxXaRR6bE7rkbitvu1WnWSr/ZYtOuPlngyYg2hOyrWFeQ7/JTFIHiu4b2m6qDwfoa9dZnzMYddctfPWlCgtWxTww6Odd7DqxzKA5VyhEKVLy3aniZMpYJSC7ccaM2Ot29/33Ia9hxyS8vyJwSZ73UIvG8uZxPqYQ/TdW3wW7YbuAEdMWEfPpOB2Y14ljuZ+hyg68ZZn07e1qfZd/OUinw2eDCvFDlfLN3SCtTEWwoDl3F+br8QhVHA8aa2BxYGsMzEsehCxYYJntGuo9Yb1qquBEc+5+upzHKNo/Nd5mg3yFfc0C2Z7QXtK2F1edfS94rbgv6DtePrCiTxVmlAqK/6NBUw7+vTvoYOiCL34bymtawjtuzsro4vxaddgNIaITgZsvDCoOpBWcW8D2U5kTG8Tdb/p0sKw4A3OX76yMj55M3AMVRY3a6oXa0GXgdK/k7X68MWc8YwLfTyfBkusYG1KENnLxEUrWQrsh3BDRYvVJ0fnFffVZvXygaMwmjQLFgdws8DXATnqnji8ltLK6k7zCnJczLiqfP6sr51jom32K0IYRvA1kAxIVW8Qk78acuj5tlJqIscDlkSKUrx6JL9+yFHa8F+1iMlyis7zFL/6rOVGYjIkFYm/WLdRjMrajTe3AjSdeMmllQzbpLweGdNZHKZBp9Qi7CORanDZJ+053XLiLT/KCvj2lV2yGYvjrO9hEBpV7AqhrlY0XbU0QuohLOVhcuB6wjMfJor6rzwiEX/jzX+Hmoqa4DUNhEwq+fGFo4tR5/zetdHRkOwzdehiWgSU/knzRjbZxypjDq/Pj5r6fPbU4TgKp9r25bO5vTVeLTSjcmm6TXPV0pRfz6vnQvHngtrV9X3pXfU5AR+EHd+e27gDCiF5K4RJknaPjtvqtD/iLfMNeLhnIOaPJjSmDp6HURdFUBIMYCocK2Vbj/4zwYbV8vTtPLcluDrQFcOB0OPi347SUs59/lk77d12UIZbC8Jub8gh8W6LCEMg42YbjyQQrTLbJQ/gP+frkyIZxmnZnNeVJ/L56tMzs5O3mkZiWHDRH8bJy/dlS/Noyyx0zXMTj4IPaWjt1du5oJTqwcXsAM58KHSe8s12in/Fr/wRaapgtd2zTgfomConix3ru9dbrjupR7/bybryz/hJwYofZYBMtGa6eL+xddkXu3AjJcGdJp5U4nTO7AHNMv3q6/ZmQh9VIJLIZ73FYxkf7GljtVLt51K7llBdxoviX8tolvLs4fDJf8tDkPCpwxXhIEypwf//wXPAU3Yw7todMGNXoCJWvYUkKY45MsuVcBFadh6ytNpu9NiYLLdNFsYlTtf8lPAHmNHuzw+mLMeSvoyis1lLa67Ak1duuvW4URdfAas0jVqKHEv5aT53UrcsWP47x8CdcLdl5BL4e3hfGTgnLth6udC1RVeHVlC5mS9f58hsHmvDqaklzs5W3X7FJ7MF2B+K0KQ08GWwoTp8eX/hGc14pqWyWcLV909s/WKFnx8RxeI5UPpB2E2Oq5clTGc6D8or8XHA6gMvm8U5A9iQ+zHCrmfPtmUt5tckFFCfvT5NT5AdsIKtkPqiGc17aXT4HblMjzObZ7R/Kt201ZfJqzbPvEMmDeBQQpIwbxJSd4mAC2/1LBCo2P23gdbjcmio4MnhmDRExfrgPwdWp9yab/WnoGM/O60BlOJDnYuf9SJiKhWtKKp8uZui4TBXmWdQYf1jUmSXE2qZryPYQ4+2H7GUZawlaqkacRihuukrpaEjwZL002q/7EszTc9UfbzeuJq73oygPKxfXdRm7A4H5CeGa4yYJhVcmHj2jbrmDh37fTY93Dp3XgTUP74jyFS447qXYTI2XBm+5d1dsH91M3L2hz+OZjFd81tOM2d2IYHNRhRvWsyNx8J7P3lbzZEmCW8rAmXw1gscLJSKriZjBU1rDltK1adKrnuXtE88EZBWEfUb0oMxvEnUKr0CbfpwasBUTI9g5x+3LpZt57zcNBJ57Zxwdp59JPkGSgCvbhVI/VGFD2eBUkNBxauMy1KmxmOoznG+EX1gRy70r74e7nD1DAbViG7NtTP0AN4upTmWEjiuz+YZ8oSHou7tCHYiipvugDtQcecouXvoEikTBznEcPoV7jSvUeOX97IGz8yDhjLO1gn6CSPEuOB5xrSlPZwtwXaE2e42XnwxU6CnRWmPS9sV5z1qR1rf5gvOF3AWsofEkr8WBaShUeak+WUOxANAFHHuAT90CXuzipX7yDGCYZudkQSpULhBTLT3eW3qTm68QK73g+QUfD/Kl9FuhBgxjdvazIU08Leamhb7tvQtq+/cIQJelLnJ5dJAOdSY/efxInXhX+fLEk8dyvWJNbeW29IIR6QuC3CXLf4R8+v7o8n0Cw0W8aaVSHVrbkSNt8QFBxx/L4jiEBppuSVBnpAFLny8fPM7RacfGB0WqJIQJx8Bh6SkuDA3VE+J1fXvs9ivj8IYfFnT12fqrswFtg6jnBic8xIHzhFm+FScvd0/FBfCmcLl04T7lG3uMKTLp15xeq3mpIZvajJZxszQdZESeEbzI71oNXJnEK5DvOkM9XjktNMjBvgN3I5C1Pr0J00TMnkZK9r2eaTmUUmec8BoVOs0a8cjXF3uGT7uOmBA0+S1dQpY0/w7/CC/xO0uX7AP9vFJpPuXhy3XbuZ+bjd7bvNsLCLvlasuN8/PWdQnxWPc2RSRxSjUEVKV1weDNmteO8+88ERm8fKjbbG+S1zQHJ8dyiDRhB3L2LCwk1sj+7cfcm707nETs1wqkbPDQ8W1CuBgyzr3o1i0Xu82L0O25eOkyN/vyxDScrFF2wxKo/wLCePXkvBHxiQAAAABJRU5ErkJggtgYWFukaGRpZ2VzdElEBGZyYW5kb21Q5levHsGtTQiVO30tX/fSmnFlbGVtZW50SWRlbnRpZmllcmpiaXJ0aF9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoxOTkwLTA0LTMw2BhYW6RoZGlnZXN0SUQRZnJhbmRvbVD6Azd6j0l9GKIjk7ytxQuvcWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlbUNvbm5vciBKdXN0aW7YGFhbpGhkaWdlc3RJRANmcmFuZG9tUOcAAr7rsAun6N013/32921xZWxlbWVudElkZW50aWZpZXJqaXNzdWVfZGF0ZWxlbGVtZW50VmFsdWXZA+xqMjAyNC0wMS0wMdgYWFOkaGRpZ2VzdElECmZyYW5kb21Q73TwdasU4qKtNarutPNAJ3FlbGVtZW50SWRlbnRpZmllcmthZ2Vfb3Zlcl8xOGxlbGVtZW50VmFsdWVkdHJ1ZdgYWFOkaGRpZ2VzdElEBWZyYW5kb21Qwa9hWf6bIkpRHSGyEvdqQ3FlbGVtZW50SWRlbnRpZmllcmthZ2Vfb3Zlcl8yMWxlbGVtZW50VmFsdWVkdHJ1ZdgYWFykaGRpZ2VzdElEDWZyYW5kb21Q+UqiYYLdKafjOiK3u7CcG3FlbGVtZW50SWRlbnRpZmllcmtleHBpcnlfZGF0ZWxlbGVtZW50VmFsdWXZA+xqMjAzNS0xMi0zMNgYWFakaGRpZ2VzdElEDmZyYW5kb21Q5NLYphEy1GcFbe0Bd4PKknFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVnSmFja21hbtgYWFikaGRpZ2VzdElEAGZyYW5kb21Q9dioUYwBh5JD/6OsXWvi1XFlbGVtZW50SWRlbnRpZmllcm1yZXNpZGVudF9jaXR5bGVsZW1lbnRWYWx1ZWdDYXBpdG9s2BhYW6RoZGlnZXN0SUQJZnJhbmRvbVDUK7BT8/P3xDW0qzC0pNVFcWVsZW1lbnRJZGVudGlmaWVybnJlc2lkZW50X3N0YXRlbGVsZW1lbnRWYWx1ZWlNb250Y2xpZmbYGFhbpGhkaWdlc3RJRAJmcmFuZG9tUBEkyO7ctwliViRxeEu3PlBxZWxlbWVudElkZW50aWZpZXJvZG9jdW1lbnRfbnVtYmVybGVsZW1lbnRWYWx1ZWhETDI0NTE5ONgYWFWkaGRpZ2VzdElECGZyYW5kb21Q3QF6v9KTgB+ZvPPb217X/nFlbGVtZW50SWRlbnRpZmllcm9pc3N1aW5nX2NvdW50cnlsZWxlbWVudFZhbHVlYkFV2BhYZKRoZGlnZXN0SUQMZnJhbmRvbVCJW7IyUANh442SHJKctLZCcWVsZW1lbnRJZGVudGlmaWVycHJlc2lkZW50X2FkZHJlc3NsZWxlbWVudFZhbHVlcDI0M0IgTWFpbiBTdHJlZXTYGFhWpGhkaWdlc3RJRAZmcmFuZG9tUPkJBaRKM6L+6swQJij7Ms1xZWxlbWVudElkZW50aWZpZXJwcmVzaWRlbnRfY291bnRyeWxlbGVtZW50VmFsdWViQVXYGFhipGhkaWdlc3RJRBBmcmFuZG9tUKOql384E7x7FwRS6kOWlEBxZWxlbWVudElkZW50aWZpZXJxaXNzdWluZ19hdXRob3JpdHlsZWxlbWVudFZhbHVlbU1vbnRjbGlmZiBETVbYGFjFpGhkaWdlc3RJRAtmcmFuZG9tUCYYfim7HoK2GzTGVky5z9xxZWxlbWVudElkZW50aWZpZXJzZHJpdmluZ19wcml2aWxlZGdlc2xlbGVtZW50VmFsdWV4bVsgICB7ICAgICAidmVoaWNsZV9jYXRlZ29yeV9jb2RlIjogIkIiLCAgICAgImlzc3VlX2RhdGUiOiAiMjAyMi0xMC0xMCIsICAgICAiZXhwaXJ5X2RhdGUiOiAiMjAzMi0xMC0xMCIgICB9IF3YGFhcpGhkaWdlc3RJRAdmcmFuZG9tUI8d/KvZyT02F8EQc8yrL3ZxZWxlbWVudElkZW50aWZpZXJ2dW5fZGlzdGluZ3Vpc2hpbmdfc2lnbmxlbGVtZW50VmFsdWViQVU=", "decoded": { "namespaces": { "org.iso.18013.5.1": [ { "digestID": 1, "random": "XYGwWHUk5ugOnrIrivXZdQ==", "elementIdentifier": "sex", "elementValue": { "type": "string", "value": "1" } }, { "digestID": 15, "random": "VHu6WoNegd/NwwUmKLfU6g==", "elementIdentifier": "portrait", "elementValue": { "type": "binary", "value": "iVBORw0KGgoAAAANSUhEUgAAAG8AAACPCAMAAADDX3XQAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAJcEhZcwAACxMAAAsTAQCanBgAAABIUExURa+uqbmMda6Ca6J2YAwIBhwTD5ZsVqqqpaOinpyblpSSjtGlkT0rIi0fGIpiTX1XQ8eZgkw3LF5CM4qJhW5LOdeyoHx6dWRhXLXA62kAABHKSURBVGjejJqJYqMgEIZRo6gBDWjl/d905+JSY5butk1zfPxzMShq1PyF/05DwRd9U0rxT03/LkNf/8Dvvw4VeYgbK1ikRUYBLqD0V52p8lhnpirJKqkT2KjT1C7vle8nQqlR6/hsxKhK5qiENRawwpwsUalar1IRm5C65GqVKGekYkZGXlmqfHWWXkm7PIyGKbmV/7SuFKpo++j+0gsqRlFlU32ewI370q/jOT7FiHe4+PzX8CSgLt9YTDXrGy/5kHTEd2Tr1HYsBGd5OZpTdhWTrhNCFVa7ZtJFUvpjHT86OzNOmDN9vFOW8k5JLqjq2VOCq1wiToFaEm9rQJHnMfhj9snUTrwcXjkDTsGaHHLjtTpAU2iDm2ngD53sWGfMOZZypKrbGqeSf4oMopfOwTtjXNjmbZunMZeIomIWs72WniuvdIH8i3E1HT4E54ztTZjnI8A4JhI7nQWqipaLvTonlUrCsjjx2XYcWzB9/+phWGfoF+sPFDpeLHpKfl0F8XUFKrUhbJy24AFBA4H8ncggclbk1zNN361aF15RMwU2gvkc6okQAiLZGmPNsW2UUNN2zIU1q/qtdFXkLgmebYmwYF9xsCjjDCFBnrMmHNs0B2ddCP4YY7yeq3hptVP1yo5DQzpbsUBT3zvvjbVIhkcAcvER0qebBqCuRKqu+MnM44SGfJG/ItQavxvr9tWjTGtkEhaH+DTMX1nZg+q0yCRc/yoGfrY1+w7UdXXOedvzX4EN6vreOPzl+ClQXXIUciBQTCYgWg8GGNOBOT1IBIEpchAEEoEMiXo86JP2R9cOhgQPRIppgEaDz1qXdV934yw4cXfWplAFtUSE4Y4wPehTRdoldVOQT4qGlAe9W4djWcl2BpJSosmSQiw/YNzezUE/+PC6xCAukpLvKCLgp9kHAGI+osyk37AP6VVh+xqlRd+XxxTYUJLdFCboPpSAaQcmxTwBSJ8jCZ71otBuUuXueOqcDYAzlSj8xwkGPAABEaLTEiXLdzEtIbL8ph54uhaoj4TrMck4tSxLRRnoIm9MH4G9GJRzgkpQGB9ysPQe/La5PiWbhQ+m8lGEDX0oRKvlwg3RklKT7YnzOZ7yr+rqRpddwsMaU6W95eItyd4nnviP/ujmH/5Ljzcb1RmaLwTFifei6GRtEKUvK9XbO66kNNyknnIiWXMMMSaxlHhc0YFnKh6YjqeEIObhy/fdif9obRy/5Xslb4qTJ56TkmHsyaLJnzidnuXt9Oq4SNrwEDGZfaT123Cs9MTjmafgj+kZeajHGxMDhgXPP+0JuWd4TYjpRjXYYmdmexvdlpb4nl+Gf5VSbvvUCRj/k6ZVSCWYSyJVFptqFT2KQP5U6zg9MT59jhcUuP206BjrtI3ZEJMiDYnB1F0YI0XIyBIhCl/Av18eigej65Od6N1G6iYWR48li6MixoSUadYuq6BYG1IibOrHmE1MPSMfYwjid7/DWNd1WSDseW3F1+EkqFAb5knJphiwx/YrQKc+V3sn6/nOY12GYei6dhiW3bsEIMl7BkZ9+P1p3WXe9iqSjywpsoDWtU3TfD5N0wFyBQwpo+fha8dYsZSyKW3M+FRjcMTsiw0CdBBgwWVBYcDi0TQtIpcFuguayYCPVnJsDBgOulk9NhaY7bGTJhc6tGIHrLZtCPemQci2hSfIxDxQc2lQnHd4tqbSIU2Nctwv5LKWLRlxkQlUlNryKIGx7H3vY6TnjPsSWc5WnD/jIu/v/QeDiR+GCg/ErlSI8nIZxmeBkzlsbm1ZXlvx/viLicwklSRwGHZXGtSGH/F5uM0UDdDO8ppCHqn7SxLfhUTkYaqYXNePH7wQKh6ac2hr9/3VxLcIbBLP58X5N+/YXLE1WTnDb+WxYUVh4g1rwYO99y97HpNPk0v6iNfE8Cx4Ud8n61txSYof4X7w9BymUHSUO5WwWt870gRX6yNe+gR3PGYfru7zceax9y4GfeesSPoW4MVNE37Cw/og1yvG7Sj3lt/1FeESA4Zq3F7w7DH+qmcAzFsC5n3Rl3Oi0Ie87D4/62dr4phMNigE6DKkbKDq8ldHaGlQ5K0+V7N+m573uTxCrjB39vy7Ecj1m/WlLdrLjuPzrpPHHOIbDMRLoe9KY4kxQJmXq4vR+rc69KBYFHZdS8z3b7xSIPFWny6gPC8OGTlJiQH30UrbPuBSjWHeQAJjMdP/4T28OCVXCgwststQ8m6BUaDoW73wvjUTl0vU48xvwcVd9H1TmAs2eLkVnn0yp672ZWJQznlH3d9A0+a+AZUW+c6iIMupmVlwgYexC2/66jl1usisuesF3oLtpqfBzdjSReIbYMOKnaGjhhBetpY8f3tnorxmn7040qUlR+923F1LewkVoGVXAo76Ff6zzIfeQeHt9/nb/vZ0iRlTkJxO86VuFoVSU40NW8fFBt2Ff6M5cD8IL0Ug8vy6H09ls77WO3icJM9WYhTKB1t2H1ruesmaMJdBmiUkrsKzoHx52N7mW4M0DrDNq/cUbtTvrtg/e9lCDNT5Ap/mg5ZELL8WTQK+MLBTXZT+fgGmvqU0wF7o1e/Cg5iwBvvfnjYT6EEITZCH3nW4r6X2nzYzyMOpwty623jRN7eH9Aaz7i018ugWY8NhzcuYQM4cIEQhXAYSPYMljPfBGATSG+Ct+Gt7e9386jwYLUzVWkk+6BD2Y6KLHVtHFbzBZQl4y7APKmBkHgMFy8IdRW/wrfPXQnaROME0DfJw+wUZuA7zBiY6Jsj5Bv4Rrxsw2z9TCHuAWAIavJrmhxctl2W813cJFrxpBu8ifVBR4CNW3rRwfcHKxTzgpsYM3EaFr0WeRd6gr/vny52meNdiGxZnAUO1E+s+Lkv4xZUSFwX8GfcOmA1DfC2kAvK6dCtP36dfSd1gJ2CYJ7s92ZSBvEb04SNe16P2hpIS/Q21tK3udnzRF+9zzkO34vLQ8Wc2uaWnT8U2raEtYWrn6VnhYdGjcLlch69vxudjBtPQLT7y8nizw0QfMmixfacXgD0hJ0FeC+ESr4rru3RP9/OIOkJgY70nHn2kjKbgkT7psfEVtDwtzBu6fOfxdCk+2zHJAwdCRmB73TbVrpYd1nwSr3ou8Tp4Y76fWF0+Lo2p0+GGcYKAg35iYIFv6axJQpv00XOyT2JHcngu3dLNX04GXLKBmWDQxbFB88aI3HfmRZys9lhtIT26MUqr7o1XN51TyIyUEQtUqIUF8r6djAbh3+IKD78V2kUemxO65G4rb7tVp1kq/2WLTrj5Z4MmINoTsq1hXkO/yUxSB4ruG9puqg8H6GvXWZ8zGHXXLXz1pQoLVsU8MOjnXew6scygOVcoRClS8t2p4mTKWCUgu3HGjNjrdvf99yGvYcckvL8icEme91CLxvLmcT6mEP03Vt8Fu2G7gBHTFhHz6TgdmNeJY7mfocoOvGWZ9O3tan2XfzlIp8NngwrxQ5Xyzd0grUxFsKA5dxfm6/EIVRwPGmtgcWBrDMxLHoQsWGCZ7RrqPWG9aqrgRHPufrqcxyjaPzXeZoN8hX3NAtme0F7SthdXnX0veK24L+g7Xj6wok8VZpQKiv+jQVMO/r076GDogi9+G8prWsI7bs7K6OL8WnXYDSGiE4GbLwwqDqQVnFvA9lOZExvE3W/6dLCsOANzl++sjI+eTNwDFUWN2uqF2tBl4HSv5O1+vDFnPGMC308nwZLrGBtShDZy8RFK1kK7IdwQ0WL1SdH5xX31Wb18oGjMJo0CxYHcLPA1wE56p44vJbSyupO8wpyXMy4qnz+rK+dY6Jt9itCGEbwNZAMSFVvEJO/GnLo+bZSaiLHA5ZEilK8eiS/fshR2vBftYjJcorO8xS/+qzlRmIyJBWJv1i3UYzK2o03twI0nXjJpZUM26S8HhnTWRymQafUIuwjkWpw2SftOd1y4i0/ygr49pVdshmL46zvYRAaVewKoa5WNF21NELqISzlYXLgesIzHyaK+q88IhF/481/h5qKmuA1DYRMKvnxhaOLUef83rXR0ZDsM3XoYloElP5J80Y22ccqYw6vz4+a+nz21OE4Cqfa9uWzub01Xi00o3Jpuk1z1dKUX8+r50Lx54La1fV96V31OQEfhB3fntu4AwoheSuESZJ2j47b6rQ/4i3zDXi4ZyDmjyY0pg6eh1EXRVASDGAqHCtlW4/+M8GG1fL07Ty3Jbg60BXDgdDj4t+O0lLOff5ZO+3ddlCGWwvCbm/IIfFuiwhDIONmG48kEK0y2yUP4D/n65MiGcZp2ZzXlSfy+erTM7OTt5pGYlhw0R/Gycv3ZUvzaMssdM1zE4+CD2lo7dXbuaCU6sHF7ADOfCh0nvLNdop/xa/8EWmqYLXds04H6JgqJ4sd67vXW647qUe/28m68s/4ScGKH2WATLRmuni/sXXZF7twIyXBnSaeVOJ0zuwBzTL96uv2ZkIfVSCSyGe9xWMZH+xpY7VS7edSu5ZQXcaL4l/LaJby7OHwyX/LQ5DwqcMV4SBMqcH//8FzwFN2MO7aHTBjV6AiVr2FJCmOOTLLlXARWnYesrTabvTYmCy3TRbGJU7X/JTwB5jR7s8PpizHkr6MorNZS2uuwJNXbrr1uFEXXwGrNI1aihxL+Wk+d1K3LFj+O8fAnXC3ZeQS+Ht4Xxk4Jy7YernQtUVXh1ZQuZkvX+fIbB5rw6mpJc7OVt1+xSezBdgfitCkNPBlsKE6fHl/4RnNeKalslnC1fdPbP1ihZ8fEcXiOVD6QdhNjquXJUxnOg/KK/FxwOoDL5vFOQPYkPsxwq5nz7ZlLebXJBRQn70+TU+QHbCCrZD6ohnNe2l0+B25TI8zm2e0fyrdtNWXyas2z7xDJg3gUEKSMG8SUneJgAtv9SwQqNj9t4HW43JoqODJ4Zg0RMX64D8HVqfcmm/1p6BjPzutAZTiQ52Ln/UiYioVrSiqfLmbouEwV5lnUGH9Y1JklxNqma8j2EOPth+xlGWsJWqpGnEYobrpK6WhI8GS9NNqv+xLM03PVH283riau96MoDysX13UZuwOB+QnhmuMmCYVXJh49o265g4d+302Pdw6d14E1D++I8hUuOO6l2EyNlwZvuXdXbB/dTNy9oc/jmYxXfNbTjNndiGBzUYUb1rMjcfCez95W82RJglvKwJl8NYLHCyUiq4mYwVNaw5bStWnSq57l7RPPBGQVhH1G9KDMbxJ1Cq9Am36cGrAVEyPYOcfty6Wbee83DQSee2ccHaefST5BkoAr24VSP1RhQ9ngVJDQcWrjMtSpsZjqM5xvhF9YEcu9K++Hu5w9QwG1YhuzbUz9ADeLqU5lhI4rs/mGfKEh6Lu7Qh2Ioqb7oA7UHHnKLl76BIpEwc5xHD6Fe40r1Hjl/eyBs/Mg4YyztYJ+gkjxLjgeca0pT2cLcF2hNnuNl58MVOgp0Vpj0vbFec9akda3+YLzhdwFrKHxJK/FgWkoVHmpPllDsQDQBRx7gE/dAl7s4qV+8gxgmGbnZEEqVC4QUy093lt6k5uvECu94PkFHw/ypfRboQYMY3b2syFNPC3mpoW+7b0Lavv3CECXpS5yeXSQDnUmP3n8SJ14V/nyxJPHcr1iTW3ltvSCEekLgtwly3+EfPr+6PJ9AsNFvGmlUh1a25EjbfEBQccfy+I4hAaabklQZ6QBS58vHzzO0WnHxgdFqiSECcfAYekpLgwN1RPidX177PYr4/CGHxZ09dn6q7MBbYOo5wYnPMSB84RZvhUnL3dPxQXwpnC5dOE+5Rt7jCky6decXqt5qSGb2oyWcbM0HWREnhG8yO9aDVyZxCuQ7zpDPV45LTTIwb4DdyOQtT69CdNEzJ5GSva9nmk5lFJnnPAaFTrNGvHI1xd7hk+7jpgQNPktXUKWNP8O/wgv8TtLl+wD/bxSaT7l4ct127mfm43e27zbCwi75WrLjfPz1nUJ8Vj3NkUkcUo1BFSldcHgzZrXjvPvPBEZvHyo22xvktc0ByfHcog0YQdy9iwsJNbI/u3H3Ju9O5xE7NcKpGzw0PFtQrgYMs696NYtF7vNi9DtuXjpMjf78sQ0nKxRdsMSqP8Cwnj15LwR8YkAAAAASUVORK5CYII=" } }, { "digestID": 4, "random": "5levHsGtTQiVO30tX/fSmg==", "elementIdentifier": "birth_date", "elementValue": { "type": "date", "value": "1990-04-30" } }, { "digestID": 17, "random": "+gM3eo9JfRiiI5O8rcULrw==", "elementIdentifier": "given_name", "elementValue": { "type": "string", "value": "Connor Justin" } }, { "digestID": 3, "random": "5wACvuuwC6fo3TXf/fb3bQ==", "elementIdentifier": "issue_date", "elementValue": { "type": "date", "value": "2024-01-01" } }, { "digestID": 10, "random": "73TwdasU4qKtNarutPNAJw==", "elementIdentifier": "age_over_18", "elementValue": { "type": "string", "value": "true" } }, { "digestID": 5, "random": "wa9hWf6bIkpRHSGyEvdqQw==", "elementIdentifier": "age_over_21", "elementValue": { "type": "string", "value": "true" } }, { "digestID": 13, "random": "+UqiYYLdKafjOiK3u7CcGw==", "elementIdentifier": "expiry_date", "elementValue": { "type": "date", "value": "2035-12-30" } }, { "digestID": 14, "random": "5NLYphEy1GcFbe0Bd4PKkg==", "elementIdentifier": "family_name", "elementValue": { "type": "string", "value": "Jackman" } }, { "digestID": 0, "random": "9dioUYwBh5JD/6OsXWvi1Q==", "elementIdentifier": "resident_city", "elementValue": { "type": "string", "value": "Capitol" } }, { "digestID": 9, "random": "1CuwU/Pz98Q1tKswtKTVRQ==", "elementIdentifier": "resident_state", "elementValue": { "type": "string", "value": "Montcliff" } }, { "digestID": 2, "random": "ESTI7ty3CWJWJHF4S7c+UA==", "elementIdentifier": "document_number", "elementValue": { "type": "string", "value": "DL245198" } }, { "digestID": 8, "random": "3QF6v9KTgB+ZvPPb217X/g==", "elementIdentifier": "issuing_country", "elementValue": { "type": "string", "value": "AU" } }, { "digestID": 12, "random": "iVuyMlADYeONkhySnLS2Qg==", "elementIdentifier": "resident_address", "elementValue": { "type": "string", "value": "243B Main Street" } }, { "digestID": 6, "random": "+QkFpEozov7qzBAmKPsyzQ==", "elementIdentifier": "resident_country", "elementValue": { "type": "string", "value": "AU" } }, { "digestID": 16, "random": "o6qXfzgTvHsXBFLqQ5aUQA==", "elementIdentifier": "issuing_authority", "elementValue": { "type": "string", "value": "Montcliff DMV" } }, { "digestID": 11, "random": "Jhh+KbsegrYbNMZWTLnP3A==", "elementIdentifier": "driving_priviledges", "elementValue": { "type": "string", "value": "[ { \"vehicle_category_code\": \"B\", \"issue_date\": \"2022-10-10\", \"expiry_date\": \"2032-10-10\" } ]" } }, { "digestID": 7, "random": "jx38q9nJPTYXwRBzzKsvdg==", "elementIdentifier": "un_distinguishing_sign", "elementValue": { "type": "string", "value": "AU" } } ] }, "issuerAuth": "hEOhASahGCFZApcwggKTMIICOKADAgECAgpSc4u6ewLWeF9jMAoGCCqGSM49BAMCMC4xCzAJBgNVBAYTAkFVMR8wHQYDVQQDDBZsYWJzLm1hdHRybGFicy5jbyBJQUNBMB4XDTI0MDcyOTEwMTMwMVoXDTI1MTAyNzEwMTMwMVowOTELMAkGA1UEBhMCQVUxKjAoBgNVBAMMIWxhYnMubWF0dHJsYWJzLmNvIERvY3VtZW50IFNpZ25lcjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIwfV3GMKmD2/AeX0nyM17WWpDcu0ow/l+LwX7JsrvMmGV2IJmkJnCLPlFJS8OEZCH2Awx6dcHZM9ypPWPxRFJ+jggExMIIBLTAdBgNVHQ4EFgQUeDSAUoc4IOG98TfH4oBwEAXzjCEwHwYDVR0jBBgwFoAUjpQIFBA5F7EMBwhiw7HRjumaE/4wDgYDVR0PAQH/BAQDAgeAMCQGA1UdEQQdMBuGGWh0dHBzOi8vbGFicy5tYXR0cmxhYnMuY28wJAYDVR0SBB0wG4YZaHR0cHM6Ly9sYWJzLm1hdHRybGFicy5jbzB4BgNVHR8EcTBvMG2ga6BphmdodHRwczovL2xhYnMudmlpLmF1MDEubWF0dHIuZ2xvYmFsL3YyL2NyZWRlbnRpYWxzL21vYmlsZS9pYWNhcy9mNDU5ZjliMi0xNzZkLTQ5NzAtYjRjYi0xZGFhOGVmOGE0ZTUvY3JsMBUGA1UdJQEB/wQLMAkGByiBjF0FAQIwCgYIKoZIzj0EAwIDSQAwRgIhAKKrbWcL0zSR7v+eMVnhvGOPfYN5Z1N7Z3bwRmfC9UTuAiEA3VS+f1ejLdHkyBKnzUjPBjOHYZ0uFX1vMS/QShgy0mNZ5MXYGFnkwKdndmVyc2lvbmMxLjBvZGlnZXN0QWxnb3JpdGhtZ1NIQS0yNTZsdmFsdWVEaWdlc3RzoXFvcmcuaXNvLjE4MDEzLjUuMbIAWCBHKXMYF97lb0Z04rfrV2P4jXk9xglx/6jKG6j2DciXxgFYIDs6fkgoiSVVbePNCUWpJI6L+rOFssU9ezcU4yNO05wnAlggo4SzZYZ40QSCJ78EpUzn96E+7t4StK9QjHI8hO2AHwMDWCBsMR9aWU8u548e7/ms/o2YTF0e5huk7SE7zaKrI/fDlARYIKHbUNrLFOo0fZ9tp0aWR7R9ivZydJzSiCMuTGFRqp5KBVggtGdZGpki+cVQ7gcwMPso1YPt7zMNrVOSPUxJjaszz7EGWCB1D6PoCZyVXGTsPIz0Vkm3PZisEJB+RerimlCCE8prcAdYILf8HxOxwVwds6KAwXCNta9aYHeqFc+7A8kLgItGmQhXCFggCAA4gbM54lbSlP0CCQ+0Xxzhqhi7lyqMdUNpQG1iOvoJWCBlKg/lHR7Q/tiC0GffuQ65zGcP6gdjvxx6pgUytNoRAgpYIJIrLr3TethAOk2L+H1niDFeh4n9S+lGsQ2TQ2W+L9mHC1gg4ysAT7MLuJGIjfa0aWmKlRvwegYldoRMrWPBa1oZp1UMWCBejb71fRUoVBycI/hb6+d9li8Nt/AGDr4cHqeFgqBNKg1YIEEWtB5rzkDXMcIL589lMjoSke6XEUextEsTR0FjpGfsDlggqhJ91EIJZrTW3ovq5Fc5yX3hl+24F0s2PM+kUXY4y0EPWCBwj8knItIZzL0BOLINHgHAIfh5pkXlftuhcEnFcxY9KxBYINfHE/iNIeoMvp5KXeNBZlfHhHIlEfvjGbsaAx1IwJN3EVggm43zZkO72fc1lwnX/8Q4O1C/BV2CMQBRlf11ZwgIp5RtZGV2aWNlS2V5SW5mb6FpZGV2aWNlS2V5pAECIAEhWCCIwikMm+n28zOyVBGiAr/UOuX81Zgv7i3PzdvwPzG5biJYIDj/fzDqOGd/zfMfj0+xRiMUMX0+Y2VIOVmS66zFm1XxZ2RvY1R5cGV2b3JnLmlzby4xODAxMy41LjEubURMCWx2YWxpZGl0eUluZm+janZhbGlkVW50aWzAdDIwMjQtMTAtMDJUMjI6Mjg6NDBaaXZhbGlkRnJvbcB0MjAyNC0wOS0wMlQyMjoyODo0MVpmc2lnbmVkwHQyMDI0LTA5LTAyVDIyOjI4OjQxWmlfYnJhbmRpbmemZG5hbWVuRHJpdmVyIExpY2Vuc2VrZGVzY3JpcHRpb254MERldGFpbHMgYWJvdXQgeW91ciBhZ2UsIGlkZW50aXR5ICYgbGljZW5zZSBjbGFzc29iYWNrZ3JvdW5kQ29sb3JnIzAwN0VBN253YXRlcm1hcmtJbWFnZaJmZm9ybWF0Y3N2Z2RkYXRhWS0FPHN2ZyB3aWR0aD0iMjQ1IiBoZWlnaHQ9IjE1MCIgdmlld0JveD0iMCAwIDI0NSAxNTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xMjEzXzU1MjApIj4KPHBhdGggZD0iTTE1NS4wNjkgNjYuODY3NUwxNDAuNDgyIDU1LjA5NjlDMTQwLjA4MiA1NC43NzQ3IDEzOS44NDggNTQuMjg3NiAxMzkuODQ4IDUzLjc3MjdMMTM5Ljg0OCA0NC42NDc5QzEzOS44NDggNDMuOTg1OCAxNDAuMjM0IDQzLjM4MiAxNDAuODM1IDQzLjEwM0wxNTMuMDQ1IDM3LjQ0MDlDMTUzLjkyNyAzNy4wMzI1IDE1NC4yODggMzUuOTY3IDE1My44MzYgMzUuMTA0NUwxNDkuNzgyIDI3LjM4NTFDMTQ5LjY1MyAyNy4xNDE2IDE0OS41ODcgMjYuODcwMiAxNDkuNTg3IDI2LjU5MzdMMTQ5LjU4NyAyMi41NDVDMTQ5LjU4NyAyMS45NDM3IDE0OS45MDcgMjEuMzg1NyAxNTAuNDI0IDIxLjA3ODdMMTc1LjM3NiA2LjM1MjcyQzE3NS45ODcgNS45OTI1IDE3Ni43NTggNi4wNDgzMSAxNzcuMzExIDYuNDk0NzhMMjAwLjk2NyAyNS42MDE4TDIwMC45NjcgMzguNzA2OEMyMDAuOTY3IDM5LjY0NzkgMjAwLjIwNiA0MC40MDg5IDE5OS4yNjUgNDAuNDA4OUwxODUuODIgNDEuMjQ1M0MxODQuODc5IDQxLjI0NTMgMTg0LjExOCA0Mi4wMDYzIDE4NC4xMTggNDIuOTQ3NUwxODMuMDA5IDU1LjU5NDFDMTgzLjAwOSA1Ni41MzUyIDE4My43NyA1Ny4yOTYyIDE4NC43MTEgNTcuMjk2MkMxODUuNjUyIDU3LjI5NjIgMTg2LjQxMyA1OC4wNTczIDE4Ni40MTMgNTguOTk4NEwxODYuNDEzIDcwLjUyNTVDMTg2LjQxMyA3MS40NjY3IDE4NS42NTIgNzIuMjI3NyAxODQuNzExIDcyLjIyNzdMMTczLjE3NiA3Mi4yMjc3QzE3My4wMTIgNzIuMjI3NyAxNzIuODQ3IDcyLjIwMjMgMTcyLjY4NyA3Mi4xNTY3TDE1NS4wNjYgNjYuODY3NSIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE5Mi44OCA1OC41MjIxTDE5Ni41OTYgNTQuMzMyNEMxOTYuNzY3IDU0LjEzOTYgMTk2Ljg0MyA1My44ODExIDE5Ni44MDUgNTMuNjI2M0wxOTUuNjYxIDQ2LjAxODhDMTk1LjYxIDQ1LjY3NjkgMTk1LjM2MSA0NS4zOTc5IDE5NS4wMjcgNDUuMzA4NUwxOTEuMjk5IDQ0LjMwOTdDMTkwLjgzNSA0NC4xODU0IDE5MC4zNTkgNDQuNDYwNiAxOTAuMjM0IDQ0LjkyNDNMMTg3Ljc4NCA1NC4wNjk2QzE4Ny42NiA1NC41MzMzIDE4Ny45MzUgNTUuMDEgMTg4LjM5OSA1NS4xMzQyTDE5MC4zODYgNTUuNjY2N0MxOTAuNzA0IDU1Ljc1MTkgMTkwLjk0NyA1Ni4wMDk1IDE5MS4wMTMgNTYuMzMyTDE5MS4zNzggNTguMTE5NkMxOTEuNTIyIDU4LjgyMTQgMTkyLjQwNSA1OS4wNTggMTkyLjg4IDU4LjUyMjFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTI2LjAxNiA5Ni4wMDk3TDEyNi40NjggODIuMjYyNUgxMzAuNTM0TDEzMy4xMTYgOTEuMDA3N0gxMzMuMjU2TDEzNS44MjcgODIuMjYyNUgxMzkuODgyTDE0MC4zNDQgOTYuMDA5N0gxMzcuNjEyTDEzNy40ODMgOTEuMDYxNUwxMzcuMzY1IDg1LjM0OTdIMTM3LjE5M0wxMzQuNDkzIDk0LjQwNjlIMTMxLjg2OEwxMjkuMTU3IDg1LjM0OTdIMTI4Ljk4NUwxMjguODY3IDkxLjA3MjNMMTI4Ljc0OSA5Ni4wMDk3SDEyNi4wMTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTQ4LjM0NiA5Ni4zMDAxQzE0Ni43MTEgOTYuMzAwMSAxNDUuNDcgOTUuODgwNiAxNDQuNjI0IDk1LjA0MTVDMTQzLjc4NSA5NC4yMDI1IDE0My4zNjUgOTMuMDA4NSAxNDMuMzY1IDkxLjQ1OTVWOTAuMDI4OUMxNDMuMzY1IDg4LjQ3MjcgMTQzLjc4NSA4Ny4yNzUxIDE0NC42MjQgODYuNDM2MUMxNDUuNDcgODUuNTg5OSAxNDYuNzExIDg1LjE2NjggMTQ4LjM0NiA4NS4xNjY4QzE0OS45NzMgODUuMTY2OCAxNTEuMjA3IDg1LjU4OTkgMTUyLjA0NiA4Ni40MzYxQzE1Mi44ODUgODcuMjc1MSAxNTMuMzA1IDg4LjQ3MjcgMTUzLjMwNSA5MC4wMjg5VjkxLjQ1OTVDMTUzLjMwNSA5My4wMDg1IDE1Mi44ODUgOTQuMjAyNSAxNTIuMDQ2IDk1LjA0MTVDMTUxLjIxNCA5NS44ODA2IDE0OS45ODEgOTYuMzAwMSAxNDguMzQ2IDk2LjMwMDFaTTE0OC4zNDYgOTQuMDk1QzE0OS4wNjMgOTQuMDk1IDE0OS42MDggOTMuODc5OCAxNDkuOTgxIDkzLjQ0OTVDMTUwLjM2MSA5My4wMTkzIDE1MC41NTEgOTIuNDAyNSAxNTAuNTUxIDkxLjU5OTRWODkuODg5QzE1MC41NTEgODkuMDcxNSAxNTAuMzYxIDg4LjQ0NzYgMTQ5Ljk4MSA4OC4wMTc0QzE0OS42MDggODcuNTc5OSAxNDkuMDYzIDg3LjM2MTIgMTQ4LjM0NiA4Ny4zNjEyQzE0Ny42MjEgODcuMzYxMiAxNDcuMDY5IDg3LjU3OTkgMTQ2LjY4OSA4OC4wMTc0QzE0Ni4zMTYgODguNDQ3NiAxNDYuMTMgODkuMDcxNSAxNDYuMTMgODkuODg5VjkxLjU5OTRDMTQ2LjEzIDkyLjQwMjUgMTQ2LjMxNiA5My4wMTkzIDE0Ni42ODkgOTMuNDQ5NUMxNDcuMDY5IDkzLjg3OTggMTQ3LjYyMSA5NC4wOTUgMTQ4LjM0NiA5NC4wOTVaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTYzLjI2OCA5Ni4wMDk3Vjg5LjY3MzlDMTYzLjI2OCA4OS4yMjkzIDE2My4yMDcgODguODQ1NiAxNjMuMDg1IDg4LjUyMjlDMTYyLjk3IDg4LjIwMDIgMTYyLjc3NiA4Ny45NDkyIDE2Mi41MDQgODcuNzdDMTYyLjIzMSA4Ny41OTA3IDE2MS44NTkgODcuNTAxIDE2MS4zODUgODcuNTAxQzE2MC45NjkgODcuNTAxIDE2MC42MDQgODcuNTc2MyAxNjAuMjg4IDg3LjcyNjlDMTU5Ljk4IDg3Ljg3NzUgMTU5LjcyNSA4OC4wODE5IDE1OS41MjQgODguMzQwMUMxNTkuMzMxIDg4LjU5MTEgMTU5LjE4NCA4OC44Nzc5IDE1OS4wODMgODkuMjAwNkwxNTguNjUzIDg3LjY5NDdIMTU5LjE2OUMxNTkuMjg0IDg3LjIyODUgMTU5LjQ3NCA4Ni44MDkgMTU5LjczOSA4Ni40MzYxQzE2MC4wMTIgODYuMDYzMiAxNjAuMzc4IDg1Ljc2OTIgMTYwLjgzNyA4NS41NTQxQzE2MS4zMDMgODUuMzMxNyAxNjEuODg0IDg1LjIyMDYgMTYyLjU3OSA4NS4yMjA2QzE2My4zOSA4NS4yMjA2IDE2NC4wNDYgODUuMzc0OCAxNjQuNTQ4IDg1LjY4MzFDMTY1LjA1IDg1Ljk4NDMgMTY1LjQxOSA4Ni40MzYxIDE2NS42NTYgODcuMDM4NUMxNjUuODk5IDg3LjY0MDkgMTY2LjAyMSA4OC4zODY3IDE2Ni4wMjEgODkuMjc1OVY5Ni4wMDk3SDE2My4yNjhaTTE1Ni4zOTQgOTYuMDA5N1Y4NS40NTcySDE1OS4xNDhMMTU5LjA0IDg4LjAyODFMMTU5LjE0OCA4OC4yNTRWOTYuMDA5N0gxNTYuMzk0WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE3My40NDkgOTYuMjM1NkMxNzIuNTUyIDk2LjIzNTYgMTcxLjgzNSA5Ni4xMDI5IDE3MS4yOTcgOTUuODM3NkMxNzAuNzY3IDk1LjU2NSAxNzAuMzgzIDk1LjE1NjMgMTcwLjE0NiA5NC42MTEzQzE2OS45MSA5NC4wNjYzIDE2OS43OTEgOTMuMzk1OCAxNjkuNzkxIDkyLjU5OThWODYuNDQ2OUgxNzIuNTI0VjkyLjE5MUMxNzIuNTI0IDkyLjc2NDcgMTcyLjY1MyA5My4xODc4IDE3Mi45MTEgOTMuNDYwM0MxNzMuMTc2IDkzLjcyNTYgMTczLjYzOSA5My44NTgzIDE3NC4yOTggOTMuODU4M0MxNzQuNjg2IDkzLjg1ODMgMTc1LjA1OSA5My44MTg5IDE3NS40MTcgOTMuNzRDMTc1Ljc3NiA5My42NTM5IDE3Ni4xMDYgOTMuNTQyOCAxNzYuNDA3IDkzLjQwNjVMMTc2LjE3IDk1LjcwODVDMTc1LjgxMiA5NS44NzM0IDE3NS4zOTkgOTYuMDAyNSAxNzQuOTMzIDk2LjA5NTdDMTc0LjQ3NCA5Ni4xODg5IDE3My45NzkgOTYuMjM1NiAxNzMuNDQ5IDk2LjIzNTZaTTE2OC4yNjQgODcuNzE2MlY4NS41NDMzSDE3Ni4zMUwxNzYuMDczIDg3LjcxNjJIMTY4LjI2NFpNMTY5LjgyNCA4NS43NDc3TDE2OS44MTMgODIuOTUwOUwxNzIuNTU2IDgyLjY3MTJMMTcyLjQ0OCA4NS43NDc3SDE2OS44MjRaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTgzLjM2NiA5Ni4yNzg2QzE4MS43MzggOTYuMjc4NiAxODAuNTMgOTUuODQ4MyAxNzkuNzQxIDk0Ljk4NzhDMTc4Ljk1OSA5NC4xMjcyIDE3OC41NjkgOTIuOTE4OSAxNzguNTY5IDkxLjM2MjdWOTAuMDcxOUMxNzguNTY5IDg4LjUyMjkgMTc4Ljk2MyA4Ny4zMjE4IDE3OS43NTIgODYuNDY4NEMxODAuNTQxIDg1LjYxNSAxODEuNzQ1IDg1LjE4ODMgMTgzLjM2NiA4NS4xODgzQzE4My43ODkgODUuMTg4MyAxODQuMTg0IDg1LjIyNDIgMTg0LjU0OSA4NS4yOTU5QzE4NC45MjIgODUuMzYwNCAxODUuMjU5IDg1LjQ1MDEgMTg1LjU2IDg1LjU2NDhDMTg1Ljg2OSA4NS42Nzk1IDE4Ni4xMzggODUuODAxNSAxODYuMzY3IDg1LjkzMDVMMTg2LjU5MyA4OC4yNDMzQzE4Ni4yNDIgODguMDIwOSAxODUuODQ3IDg3LjgzNDUgMTg1LjQxIDg3LjY4MzlDMTg0Ljk4IDg3LjUzMzMgMTg0LjQ4MSA4Ny40NTggMTgzLjkxNSA4Ny40NThDMTgzLjAyNSA4Ny40NTggMTgyLjM3MyA4Ny42ODc1IDE4MS45NTcgODguMTQ2NEMxODEuNTQ4IDg4LjU5ODIgMTgxLjM0NCA4OS4yNTggMTgxLjM0NCA5MC4xMjU3VjkxLjI2NTlDMTgxLjM0NCA5Mi4xMjY1IDE4MS41NTUgOTIuNzg5OCAxODEuOTc4IDkzLjI1NTlDMTgyLjQwOSA5My43MjIgMTgzLjA2OCA5My45NTUxIDE4My45NTggOTMuOTU1MUMxODQuNTI0IDkzLjk1NTEgMTg1LjAyNiA5My44ODM0IDE4NS40NjQgOTMuNzRDMTg1LjkwMSA5My41ODk0IDE4Ni4zMSA5My40MDY1IDE4Ni42OSA5My4xOTE0TDE4Ni40NjQgOTUuNTE0OEMxODYuMTEzIDk1LjcxNTYgMTg1LjY3MiA5NS44OTEzIDE4NS4xNDEgOTYuMDQxOUMxODQuNjEgOTYuMTk5NyAxODQuMDE5IDk2LjI3ODYgMTgzLjM2NiA5Ni4yNzg2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE4OS41ODEgOTYuMDA5N1Y4MS43ODkySDE5Mi4zNDVWOTYuMDA5N0gxODkuNTgxWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE5NS44ODggOTYuMDA5N1Y4NS40NTcySDE5OC42NDJWOTYuMDA5N0gxOTUuODg4Wk0xOTcuMjY1IDg0LjIwOTVDMTk2Ljc0MSA4NC4yMDk1IDE5Ni4zNTQgODQuMDg3NSAxOTYuMTAzIDgzLjg0MzdDMTk1Ljg1OSA4My41OTI3IDE5NS43MzcgODMuMjQ4NSAxOTUuNzM3IDgyLjgxMTFWODIuNzU3M0MxOTUuNzM3IDgyLjMxOTggMTk1Ljg1OSA4MS45NzU2IDE5Ni4xMDMgODEuNzI0NkMxOTYuMzU0IDgxLjQ3MzYgMTk2Ljc0MSA4MS4zNDgxIDE5Ny4yNjUgODEuMzQ4MUMxOTcuNzgxIDgxLjM0ODEgMTk4LjE2NSA4MS40NzM2IDE5OC40MTYgODEuNzI0NkMxOTguNjY3IDgxLjk3NTYgMTk4Ljc5MiA4Mi4zMTk4IDE5OC43OTIgODIuNzU3M1Y4Mi44MTExQzE5OC43OTIgODMuMjU1NyAxOTguNjY3IDgzLjU5OTkgMTk4LjQxNiA4My44NDM3QzE5OC4xNjUgODQuMDg3NSAxOTcuNzgxIDg0LjIwOTUgMTk3LjI2NSA4NC4yMDk1WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTIwNS44NDMgODEuNDU1N0MyMDYuNDAyIDgxLjQ1NTcgMjA2LjkxOCA4MS41MDIzIDIwNy4zOTIgODEuNTk1NkMyMDcuODY1IDgxLjY4ODggMjA4LjI4NCA4MS44MDM1IDIwOC42NSA4MS45Mzk4TDIwOC44OTcgODQuMDA1MUMyMDguNTg5IDgzLjkxMTggMjA4LjI2NiA4My44MzY1IDIwNy45MjkgODMuNzc5MkMyMDcuNTk5IDgzLjcxNDYgMjA3LjIzNCA4My42ODI0IDIwNi44MzIgODMuNjgyNEMyMDYuMzUyIDgzLjY4MjQgMjA1Ljk3MiA4My43MzYyIDIwNS42OTIgODMuODQzN0MyMDUuNDE5IDgzLjk1MTMgMjA1LjIyNiA4NC4xMDU1IDIwNS4xMTEgODQuMzA2M0MyMDQuOTk2IDg0LjUwNzEgMjA0LjkzOSA4NC43NDM3IDIwNC45MzkgODUuMDE2MlY4NS4wNDg1QzIwNC45MzkgODUuMjQyMSAyMDQuOTY4IDg1LjQyNSAyMDUuMDI1IDg1LjU5NzFDMjA1LjA4MiA4NS43NjkyIDIwNS4xNTEgODUuOTIzNCAyMDUuMjI5IDg2LjA1OTZMMjAzLjQzMyA4Ni4xMjQyVjg1LjgzMzdDMjAzLjEwMyA4NS42NzYgMjAyLjgyMyA4NS40NDI5IDIwMi41OTQgODUuMTM0NUMyMDIuMzY1IDg0LjgyNjIgMjAyLjI1IDg0LjQ0MjUgMjAyLjI1IDgzLjk4MzZWODMuOTI5OEMyMDIuMjUgODMuMTY5NiAyMDIuNTQgODIuNTY3MiAyMDMuMTIxIDgyLjEyMjZDMjAzLjcwOSA4MS42NzggMjA0LjYxNiA4MS40NTU3IDIwNS44NDMgODEuNDU1N1pNMjAyLjYxNiA5Ni4wMDk3Vjg2LjY5NDNIMjA1LjM1OFY5Ni4wMDk3SDIwMi42MTZaTTIwMS4wODggODguMDkyN1Y4NS45MDlMMjAzLjc1NiA4NS45MzA1TDIwNC44MzEgODUuOTA5SDIwOC44NzZMMjA4LjYzOSA4OC4wOTI3SDIwMS4wODhaTTIxMi43NDggODEuNDU1N0MyMTMuMzA4IDgxLjQ1NTcgMjEzLjgyNCA4MS41MDIzIDIxNC4yOTcgODEuNTk1NkMyMTQuNzcxIDgxLjY4ODggMjE1LjE5IDgxLjgwMzUgMjE1LjU1NiA4MS45Mzk4TDIxNS44MDMgODQuMDA1MUMyMTUuNDk1IDgzLjkxMTggMjE1LjE3MiA4My44MzY1IDIxNC44MzUgODMuNzc5MkMyMTQuNTA1IDgzLjcxNDYgMjE0LjE0IDgzLjY4MjQgMjEzLjczOCA4My42ODI0QzIxMy4yNTggODMuNjgyNCAyMTIuODc3IDgzLjczNjIgMjEyLjU5OCA4My44NDM3QzIxMi4zMjUgODMuOTUxMyAyMTIuMTMyIDg0LjEwNTUgMjEyLjAxNyA4NC4zMDYzQzIxMS45MDIgODQuNTA3MSAyMTEuODQ1IDg0Ljc0MzcgMjExLjg0NSA4NS4wMTYyVjg1LjA0ODVDMjExLjg0NSA4NS4yNDIxIDIxMS44NzQgODUuNDI1IDIxMS45MzEgODUuNTk3MUMyMTEuOTg4IDg1Ljc2OTIgMjEyLjA1NiA4NS45MjM0IDIxMi4xMzUgODYuMDU5NkwyMTAuMzM5IDg2LjEyNDJWODUuODMzN0MyMTAuMDA5IDg1LjY3NiAyMDkuNzI5IDg1LjQ0MjkgMjA5LjUgODUuMTM0NUMyMDkuMjcgODQuODI2MiAyMDkuMTU2IDg0LjQ0MjUgMjA5LjE1NiA4My45ODM2VjgzLjkyOThDMjA5LjE1NiA4My4xNjk2IDIwOS40NDYgODIuNTY3MiAyMTAuMDI3IDgyLjEyMjZDMjEwLjYxNSA4MS42NzggMjExLjUyMiA4MS40NTU3IDIxMi43NDggODEuNDU1N1pNMjA5LjUyMSA5Ni4wMDk3Vjg2LjY5NDNIMjEyLjI2NFY5Ni4wMDk3SDIwOS41MjFaTTIwNy45OTQgODguMDkyN1Y4NS45MDlMMjEwLjY2MiA4NS45MzA1TDIxMS43MzcgODUuOTA5SDIxNS43ODJMMjE1LjU0NSA4OC4wOTI3SDIwNy45OTRaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTQ2LjY3NCAxMTkuNzQ0VjExNi4yNDZIMTUwLjM5OUMxNTEuNDY3IDExNi4yNDYgMTUyLjI2OCAxMTUuOTU3IDE1Mi44MDIgMTE1LjM3OEMxNTMuMzM2IDExNC44IDE1My42MDMgMTEzLjk3MiAxNTMuNjAzIDExMi44OTVWMTA5LjQ3N0MxNTMuNjAzIDEwOC40IDE1My4zMzYgMTA3LjU3NyAxNTIuODAyIDEwNy4wMDhDMTUyLjI2OCAxMDYuNDI5IDE1MS40NjcgMTA2LjE0IDE1MC4zOTkgMTA2LjE0SDE0Ni42NjFWMTAyLjY4MkgxNTAuNTQ2QzE1Mi45NzYgMTAyLjY4MiAxNTQuNzkxIDEwMy4yNyAxNTUuOTkzIDEwNC40NDRDMTU3LjIwMyAxMDUuNjEgMTU3LjgwOSAxMDcuMjg4IDE1Ny44MDkgMTA5LjQ3N1YxMTIuOTA4QzE1Ny44MDkgMTE1LjEwNyAxNTcuMjA4IDExNi43OTggMTU2LjAwNiAxMTcuOTgxQzE1NC44MDUgMTE5LjE1NiAxNTIuOTg1IDExOS43NDQgMTUwLjU0NiAxMTkuNzQ0SDE0Ni42NzRaTTE0My43NTEgMTE5Ljc0NFYxMDIuNjgySDE0Ny44NjNWMTE5Ljc0NEgxNDMuNzUxWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE2MS40MyAxMTkuNzQ0TDE2MS45NjQgMTAyLjY4MkgxNjcuNzg1TDE3MC42MDIgMTEyLjg1NUgxNzAuNzc1TDE3My41OTIgMTAyLjY4MkgxNzkuNDEzTDE3OS45NDcgMTE5Ljc0NEgxNzUuOTAyTDE3NS43ODIgMTE0LjE1TDE3NS42NjIgMTA3LjM0MUgxNzUuNDYxTDE3Mi41MzggMTE3Ljc0MUgxNjguODI2TDE2NS45MDMgMTA3LjM0MUgxNjUuNjg5TDE2NS41ODIgMTE0LjE2M0wxNjUuNDc1IDExOS43NDRIMTYxLjQzWiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE4Ny4zMjEgMTE5Ljc0NEwxODIuNjQ5IDEwMi42ODJIMTg2Ljk3NEwxOTAuMzI1IDExNi42MzNIMTkwLjYzMkwxOTMuOTk2IDEwMi42ODJIMTk4LjMwOEwxOTMuNjQ5IDExOS43NDRIMTg3LjMyMVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xOTguNzg0IC0xMS43MzYzTDE5NS42MTEgLTguNzA2MThMMTkxLjMxMSAtOS42MDcwM0wxOTMuMjE1IC01LjYzNTA4TDE5MS4wNDUgLTEuODI2OTJMMTk1LjM4NiAtMi40MjA2NkwxOTguMzU0IDAuODM0NzAxTDE5OS4xMzIgLTMuNDg1MzFMMjAzLjE0NSAtNS4yODcwMkwxOTkuMjc2IC03LjM3NTM3TDE5OC43ODQgLTExLjczNjNaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjIzLjcwMSAyLjc2MTMzTDIxOS42NjggNC41NDI1NkwyMTUuOTQxIDIuMjI5TDIxNi4zNzEgNi41ODk5NkwyMTMuMDE0IDkuNDM1ODVMMjE3LjMxMyAxMC4zNzc3TDIxOC45NzIgMTQuNDMxNUwyMjEuMTgzIDEwLjY0MzhMMjI1LjU2NCAxMC4zMTYyTDIyMi42NTcgNy4wNDAzOUwyMjMuNzAxIDIuNzYxMzNaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjQyLjEyNyAyNC45MTQyTDIzNy43NDYgMjUuMjAwOUwyMzUuMDIzIDIxLjc2MTJMMjMzLjkzOCAyNi4wMTk4TDIyOS44MjIgMjcuNTM0OUwyMzMuNTI4IDI5Ljg2ODlMMjMzLjcxMiAzNC4yNzA4TDIzNy4wOTEgMzEuNDY1OUwyNDEuMzA4IDMyLjY1MzRMMjM5LjY5MSAyOC41NzkxTDI0Mi4xMjcgMjQuOTE0MloiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0yNTEuODcyIDUyLjAzODlMMjQ3LjY3NSA1MC44MTA1TDI0Ni4yODMgNDYuNjMzOEwyNDMuODA2IDUwLjI1NzdMMjM5LjQyNCA1MC4yNzgyTDI0Mi4xMDYgNTMuNzU4N0wyNDAuNzc2IDU3LjkzNTRMMjQ0LjkxMSA1Ni40NjEzTDI0OC40NzQgNTkuMDIwNkwyNDguMzUxIDU0LjYzOTFMMjUxLjg3MiA1Mi4wMzg5WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTI1MS43NzEgODAuODQ1OUwyNDguMjI5IDc4LjI0NTdMMjQ4LjM3MyA3My44NjQzTDI0NC43OSA3Ni40MjM1TDI0MC42NTQgNzQuOTQ5NEwyNDIuMDA1IDc5LjEyNjFMMjM5LjMwMyA4Mi42MDY3TDI0My43MDUgODIuNjI3MUwyNDYuMTgyIDg2LjI1MUwyNDcuNTU0IDgyLjA3NDNMMjUxLjc3MSA4MC44NDU5WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTI0MS44MDEgMTA3Ljg5NUwyMzkuMzY1IDEwNC4yNTFMMjQxLjAwMiAxMDAuMTU2TDIzNi43NjQgMTAxLjM2NEwyMzMuMzg2IDk4LjUzODZMMjMzLjIyMiAxMDIuOTRMMjI5LjQ5NiAxMDUuMjc1TDIzMy42MzIgMTA2LjgxTDIzNC42OTYgMTExLjA2OUwyMzcuNDIgMTA3LjYwOUwyNDEuODAxIDEwNy44OTVaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMjIzLjIxMSAxMjkuOTA0TDIyMi4xNjYgMTI1LjY0NUwyMjUuMDc0IDEyMi4zNDlMMjIwLjY5MiAxMjIuMDIxTDIxOC40ODEgMTE4LjIzM0wyMTYuODIzIDEyMi4zMDhMMjEyLjUyMyAxMjMuMjI5TDIxNS44ODEgMTI2LjA3NUwyMTUuNDUxIDEzMC40NTZMMjE5LjE3NyAxMjguMTQzTDIyMy4yMTEgMTI5LjkwNFoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xOTguMTkxIDE0NC4yMTRMMTk4LjY2MSAxMzkuODUzTDIwMi41MzEgMTM3Ljc2NUwxOTguNTM5IDEzNS45NjNMMTk3Ljc0IDEzMS42NDNMMTk0Ljc5MiAxMzQuODk4TDE5MC40MzEgMTM0LjMwNUwxOTIuNjIyIDEzOC4xMTNMMTkwLjcxOCAxNDIuMDY0TDE5NS4wMTcgMTQxLjE4NEwxOTguMTkxIDE0NC4yMTRaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTY5Ljc5NCAxNDkuMTA2TDE3MS43MzkgMTQ1LjE3NUwxNzYuMDggMTQ0LjU0MUwxNzIuOTI3IDE0MS40NjlMMTczLjY4NCAxMzcuMTQ5TDE2OS43OTQgMTM5LjE5N0wxNjUuOTA0IDEzNy4xNDlMMTY2LjY0MSAxNDEuNDY5TDE2My40ODggMTQ0LjU0MUwxNjcuODQ5IDE0NS4xNzVMMTY5Ljc5NCAxNDkuMTA2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTE0MS40MTggMTQzLjk4OUwxNDQuNTkxIDE0MC45NThMMTQ4Ljg5MSAxNDEuODU5TDE0Ni45ODcgMTM3Ljg4N0wxNDkuMTc3IDEzNC4wNzlMMTQ0LjgxNiAxMzQuNjczTDE0MS44NjggMTMxLjQxN0wxNDEuMDcgMTM1LjczN0wxMzcuMDc3IDEzNy41NkwxNDAuOTQ3IDEzOS42MjhMMTQxLjQxOCAxNDMuOTg5WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTExNi41MiAxMjkuNDk1TDEyMC41MzMgMTI3LjczNEwxMjQuMjc5IDEzMC4wMjdMMTIzLjgyOSAxMjUuNjY2TDEyNy4xODcgMTIyLjgyTDEyMi44ODcgMTIxLjg5OUwxMjEuMjI5IDExNy44MjVMMTE5LjAxOCAxMjEuNjEyTDExNC42MzYgMTIxLjk0TDExNy41NjQgMTI1LjIxNkwxMTYuNTIgMTI5LjQ5NVoiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik05OC4wNzQgMTA3LjM0TDEwMi40NTUgMTA3LjA1NEwxMDUuMTc4IDExMC41MTRMMTA2LjI2NCAxMDYuMjU1TDExMC4zNzkgMTA0LjcyTDEwNi42NzMgMTAyLjM4NkwxMDYuNTA5IDk3Ljk4MzlMMTAzLjEzMSAxMDAuNzg5TDk4Ljg5MjkgOTkuNjAxM0wxMDAuNTEgMTAzLjY5Nkw5OC4wNzQgMTA3LjM0WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTg4LjMyNzYgODAuMjEyMUw5Mi41NDUzIDgxLjQ2MUw5My45MTcgODUuNjE3Mkw5Ni4zOTQ0IDgxLjk5MzNMMTAwLjc5NiA4MS45NzI5TDk4LjA5MzcgNzguNDkyM0w5OS40NDUgNzQuMzE1Nkw5NS4zMDkzIDc1Ljc4OTdMOTEuNzI2MyA3My4yMzA1TDkxLjg2OTYgNzcuNjMyNEw4OC4zMjc2IDgwLjIxMjFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNODguNDMwNyA1MS40MDU2TDkxLjk3MjcgNTQuMDA1OEw5MS44NDk4IDU4LjM4NzJMOTUuNDEyMyA1NS44MjhMOTkuNTQ4IDU3LjMwMjFMOTguMjE3MiA1My4xMjU0TDEwMC44OTkgNDkuNjQ0OUw5Ni40OTc0IDQ5LjYyNDRMOTQuMDQwNSA0Ni4wMDA1TDkyLjY0ODMgNTAuMTc3Mkw4OC40MzA3IDUxLjQwNTZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNOTguMzk5OSAyNC4zNkwxMDAuODM2IDI4LjAwNDRMOTkuMjE4OSAzMi4wOTkyTDEwMy40MzcgMzAuOTExN0wxMDYuODE1IDMzLjcxNjZMMTA2Ljk5OSAyOS4zMTQ3TDExMC43MDUgMjYuOTgwN0wxMDYuNTg5IDI1LjQ0NTFMMTA1LjUwNCAyMS4xODY1TDEwMi43ODEgMjQuNjQ2Nkw5OC4zOTk5IDI0LjM2WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTExNy4wMTIgMi4zNTE2NkwxMTguMDU2IDYuNjMwNzJMMTE1LjEyOCA5LjkwNjU2TDExOS41MSAxMC4yMzQxTDEyMS43MjEgMTQuMDIxOEwxMjMuNCA5Ljk0NzUxTDEyNy42NzkgOS4wMjYxOEwxMjQuMzIxIDYuMTgwM0wxMjQuNzcyIDEuODE5MzRMMTIxLjAyNSA0LjExMjQyTDExNy4wMTIgMi4zNTE2NloiIGZpbGw9IndoaXRlIi8+CjxwYXRoIGQ9Ik0xNDIuMDMgLTExLjk2MTRMMTQxLjUzOSAtNy42MDA0N0wxMzcuNjY5IC01LjUxMjEyTDE0MS42ODIgLTMuNzEwNDFMMTQyLjQ2IDAuNjA5NjAzTDE0NS40MjkgLTIuNjQ1NzZMMTQ5Ljc3IC0yLjA1MjAxTDE0Ny41OTkgLTUuODYwMThMMTQ5LjUwMyAtOS44MTE2NkwxNDUuMjA0IC04LjkzMTI4TDE0Mi4wMyAtMTEuOTYxNFoiIGZpbGw9IndoaXRlIi8+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMTIxM181NTIwIj4KPHJlY3Qgd2lkdGg9IjI0NSIgaGVpZ2h0PSIxNDguNzUiIGZpbGw9IndoaXRlIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwIDAuMzU2NDQ1KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgpqaXNzdWVySWNvbqJmZm9ybWF0Y3N2Z2RkYXRhWTGYPHN2ZyB3aWR0aD0iMzMiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMyAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE2LjY2NjUgMzJDMjUuNTAzMSAzMiAzMi42NjY1IDI0LjgzNjYgMzIuNjY2NSAxNkMzMi42NjY1IDcuMTYzNDQgMjUuNTAzMSAwIDE2LjY2NjUgMEM3LjgyOTk1IDAgMC42NjY1MDQgNy4xNjM0NCAwLjY2NjUwNCAxNkMwLjY2NjUwNCAyNC44MzY2IDcuODI5OTUgMzIgMTYuNjY2NSAzMloiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTE2LjYxNzMgMzAuMjY0NEMyNC40ODY4IDMwLjI2NDQgMzAuODY2MiAyMy44ODUgMzAuODY2MiAxNi4wMTU1QzMwLjg2NjIgOC4xNDYwNSAyNC40ODY4IDEuNzY2NiAxNi42MTczIDEuNzY2NkM4Ljc0Nzg2IDEuNzY2NiAyLjM2ODQxIDguMTQ2MDUgMi4zNjg0MSAxNi4wMTU1QzIuMzY4NDEgMjMuODg1IDguNzQ3ODYgMzAuMjY0NCAxNi42MTczIDMwLjI2NDRaIiBmaWxsPSIjMDAzNDU5Ii8+CjxwYXRoIGQ9Ik0xNi42MTczIDEwLjQwNzdMMjUuMzMyMyAxMC44OTQ1TDE2LjYxNzMgMTEuMzgxNEw3LjkwMjM0IDEwLjg5NDVMMTYuNjE3MyAxMC40MDc3WiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMTYuNjE3MyAyMS40OTA3TDcuOTAyMjcgMjEuMDAzOUwxNi42MTczIDIwLjUxNzFMMjUuMzMyMyAyMS4wMDM5TDE2LjYxNzMgMjEuNDkwN1oiIGZpbGw9IiMwQTRCNzciLz4KPHBhdGggZD0iTTE2LjYxNzMgOC44MzkzNkwyMy42MDQ1IDkuMzI2MThMMTYuNjE3MyA5LjgxMzAxTDkuNjMwMTMgOS4zMjYxOEwxNi42MTczIDguODM5MzZaIiBmaWxsPSIjMEE0Qjc3Ii8+CjxwYXRoIGQ9Ik0xNi42MTczIDIzLjA1OTFMOS42MzAxNiAyMi41NzIzTDE2LjYxNzMgMjIuMDg1NEwyMy42MDQ1IDIyLjU3MjNMMTYuNjE3MyAyMy4wNTkxWiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMTYuNjE3MyA3LjI3MUwyMS42MzcyIDcuNzU3ODJMMTYuNjE3MyA4LjI0NDY1TDExLjU5NzQgNy43NTc4MkwxNi42MTczIDcuMjcxWiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMTYuNjE3MyAyNC42MjdMMTEuNTk3NCAyNC4xNDAxTDE2LjYxNzMgMjMuNjUzM0wyMS42MzcyIDI0LjE0MDFMMTYuNjE3MyAyNC42MjdaIiBmaWxsPSIjMEE0Qjc3Ii8+CjxwYXRoIGQ9Ik0zMC4yNDMyIDE1Ljc4MkMzMC4yNDMyIDIzLjMwNTMgMjQuMTQxMSAyOS40MDM3IDE2LjYxNzggMjkuNDAzN0M5LjA5NDU2IDI5LjQwMzcgMi45OTYwOSAyMy4zMDUzIDIuOTk2MDkgMTUuNzgyQzIuOTk2MDkgMTUuMjYxOSAzLjAyNTQgMTQuNzQ5MSAzLjA4NCAxNC4yNDM3QzMuODQ5NTEgMjEuMDQxNyA5LjYxNDY3IDI2LjMyMzQgMTYuNjE3OCAyNi4zMjM0QzIzLjYyMSAyNi4zMjM0IDI5LjM4OTggMjEuMDQxNyAzMC4xNTUzIDE0LjI0MzdDMzAuMjEzOSAxNC43NDkxIDMwLjI0MzIgMTUuMjYxOSAzMC4yNDMyIDE1Ljc4MloiIGZpbGw9IiMwMDJENEQiLz4KPHBhdGggZD0iTTE0LjE4NjkgMTYuMTQxMkwxMS44Mjg5IDE0LjIzODRDMTEuNzY0MSAxNC4xODYzIDExLjcyNjMgMTQuMTA3NiAxMS43MjYzIDE0LjAyNDNMMTEuNzI2MyAxMi41NDkyQzExLjcyNjMgMTIuNDQyMiAxMS43ODg3IDEyLjM0NDYgMTEuODg1OSAxMi4yOTk1TDEzLjg1OTYgMTEuMzg0MUMxNC4wMDI0IDExLjMxODEgMTQuMDYwNiAxMS4xNDU5IDEzLjk4NzYgMTEuMDA2NUwxMy4zMzIzIDkuNzU4NTRDMTMuMzExMyA5LjcxOTE3IDEzLjMwMDcgOS42NzUyOSAxMy4zMDA3IDkuNjMwNTlMMTMuMzAwNyA4Ljk3NjA5QzEzLjMwMDcgOC44Nzg4OSAxMy4zNTI0IDguNzg4NjcgMTMuNDM2IDguNzM5MDVMMTcuNDY5NyA2LjM1ODQ3QzE3LjU2ODUgNi4zMDAyMyAxNy42OTMyIDYuMzA5MjYgMTcuNzgyNiA2LjM4MTQzTDIxLjYwNjcgOS40NzAyNUwyMS42MDY3IDExLjU4ODhDMjEuNjA2NyAxMS43NDA5IDIxLjQ4MzcgMTEuODY0IDIxLjMzMTUgMTEuODY0TDE5LjE1ODEgMTEuOTk5MkMxOS4wMDU5IDExLjk5OTIgMTguODgyOSAxMi4xMjIyIDE4Ljg4MjkgMTIuMjc0M0wxOC43MDM2IDE0LjMxODhDMTguNzAzNiAxNC40NzA5IDE4LjgyNjcgMTQuNTkzOSAxOC45Nzg4IDE0LjU5MzlDMTkuMTMxIDE0LjU5MzkgMTkuMjU0IDE0LjcxNyAxOS4yNTQgMTQuODY5MUwxOS4yNTQgMTYuNzMyNkMxOS4yNTQgMTYuODg0NyAxOS4xMzEgMTcuMDA3NyAxOC45Nzg4IDE3LjAwNzdMMTcuMTE0MSAxNy4wMDc3QzE3LjA4NzUgMTcuMDA3NyAxNy4wNjA4IDE3LjAwMzYgMTcuMDM1IDE2Ljk5NjNMMTQuMTg2NSAxNi4xNDEyIiBmaWxsPSIjMDBBOEU4Ii8+CjxwYXRoIGQ9Ik0yMC4yOTk2IDE0Ljc5MjNMMjAuOTAwMyAxNC4xMTVDMjAuOTI3OSAxNC4wODM4IDIwLjk0MDMgMTQuMDQyIDIwLjkzNDEgMTQuMDAwOEwyMC43NDkyIDEyLjc3MUMyMC43NDA5IDEyLjcxNTcgMjAuNzAwNiAxMi42NzA2IDIwLjY0NjYgMTIuNjU2MkwyMC4wNDQgMTIuNDk0N0MxOS45NjkgMTIuNDc0NiAxOS44OTIgMTIuNTE5MSAxOS44NzE5IDEyLjU5NDFMMTkuNDc1OCAxNC4wNzI1QzE5LjQ1NTcgMTQuMTQ3NSAxOS41MDAyIDE0LjIyNDUgMTkuNTc1MSAxNC4yNDQ2TDE5Ljg5NjQgMTQuMzMwN0MxOS45NDc4IDE0LjM0NDUgMTkuOTg3IDE0LjM4NjEgMTkuOTk3NyAxNC40MzgyTDIwLjA1NjggMTQuNzI3MkMyMC4wOCAxNC44NDA3IDIwLjIyMjggMTQuODc4OSAyMC4yOTk2IDE0Ljc5MjNaIiBmaWxsPSIjMDBBOEU4Ii8+CjxwYXRoIGQ9Ik05LjQ5MDQxIDIxLjAwNzhMOS41NjM0NCAxOC43ODU1SDEwLjIyMDhMMTAuNjM4MSAyMC4xOTkySDEwLjY2MDdMMTEuMDc2MyAxOC43ODU1SDExLjczMTlMMTEuODA2NyAyMS4wMDc4SDExLjM2NUwxMS4zNDQxIDIwLjIwNzlMMTEuMzI1IDE5LjI4NDVIMTEuMjk3MkwxMC44NjA3IDIwLjc0ODdIMTAuNDM2NEw5Ljk5ODE4IDE5LjI4NDVIOS45NzAzNUw5Ljk1MTIzIDIwLjIwOTZMOS45MzIxIDIxLjAwNzhIOS40OTA0MVpNMTMuMTAwMSAyMS4wNTQ4QzEyLjgzNTggMjEuMDU0OCAxMi42MzUzIDIwLjk4NjkgMTIuNDk4NSAyMC44NTEzQzEyLjM2MjggMjAuNzE1NyAxMi4yOTUgMjAuNTIyNyAxMi4yOTUgMjAuMjcyMlYyMC4wNDFDMTIuMjk1IDE5Ljc4OTQgMTIuMzYyOCAxOS41OTU4IDEyLjQ5ODUgMTkuNDYwMkMxMi42MzUzIDE5LjMyMzQgMTIuODM1OCAxOS4yNTUgMTMuMTAwMSAxOS4yNTVDMTMuMzYzMyAxOS4yNTUgMTMuNTYyNyAxOS4zMjM0IDEzLjY5ODMgMTkuNDYwMkMxMy44MzQgMTkuNTk1OCAxMy45MDE4IDE5Ljc4OTQgMTMuOTAxOCAyMC4wNDFWMjAuMjcyMkMxMy45MDE4IDIwLjUyMjcgMTMuODM0IDIwLjcxNTcgMTMuNjk4MyAyMC44NTEzQzEzLjU2MzggMjAuOTg2OSAxMy4zNjQ0IDIxLjA1NDggMTMuMTAwMSAyMS4wNTQ4Wk0xMy4xMDAxIDIwLjY5ODNDMTMuMjE2MSAyMC42OTgzIDEzLjMwNDIgMjAuNjYzNSAxMy4zNjQ0IDIwLjU5MzlDMTMuNDI1OSAyMC41MjQ0IDEzLjQ1NjYgMjAuNDI0NyAxMy40NTY2IDIwLjI5NDlWMjAuMDE4NEMxMy40NTY2IDE5Ljg4NjIgMTMuNDI1OSAxOS43ODUzIDEzLjM2NDQgMTkuNzE1OEMxMy4zMDQyIDE5LjY0NTEgMTMuMjE2MSAxOS42MDk3IDEzLjEwMDEgMTkuNjA5N0MxMi45ODMgMTkuNjA5NyAxMi44OTM4IDE5LjY0NTEgMTIuODMyMyAxOS43MTU4QzEyLjc3MjEgMTkuNzg1MyAxMi43NDE5IDE5Ljg4NjIgMTIuNzQxOSAyMC4wMTg0VjIwLjI5NDlDMTIuNzQxOSAyMC40MjQ3IDEyLjc3MjEgMjAuNTI0NCAxMi44MzIzIDIwLjU5MzlDMTIuODkzOCAyMC42NjM1IDEyLjk4MyAyMC42OTgzIDEzLjEwMDEgMjAuNjk4M1pNMTUuNTEyNCAyMS4wMDc4VjE5Ljk4MzZDMTUuNTEyNCAxOS45MTE3IDE1LjUwMjYgMTkuODQ5NyAxNS40ODI4IDE5Ljc5NzVDMTUuNDY0MyAxOS43NDUzIDE1LjQzMyAxOS43MDQ4IDE1LjM4ODkgMTkuNjc1OEMxNS4zNDQ5IDE5LjY0NjggMTUuMjg0NiAxOS42MzIzIDE1LjIwODEgMTkuNjMyM0MxNS4xNDA5IDE5LjYzMjMgMTUuMDgxNyAxOS42NDQ1IDE1LjAzMDcgMTkuNjY4OEMxNC45ODA5IDE5LjY5MzIgMTQuOTM5NyAxOS43MjYyIDE0LjkwNzMgMTkuNzY4QzE0Ljg3NiAxOS44MDg1IDE0Ljg1MjIgMTkuODU0OSAxNC44MzYgMTkuOTA3MUwxNC43NjY0IDE5LjY2MzZIMTQuODQ5OUMxNC44Njg0IDE5LjU4ODMgMTQuODk5MSAxOS41MjA0IDE0Ljk0MiAxOS40NjAyQzE0Ljk4NjEgMTkuMzk5OSAxNS4wNDUyIDE5LjM1MjQgMTUuMTE5NCAxOS4zMTc2QzE1LjE5NDggMTkuMjgxNiAxNS4yODg3IDE5LjI2MzcgMTUuNDAxMSAxOS4yNjM3QzE1LjUzMjEgMTkuMjYzNyAxNS42MzgyIDE5LjI4ODYgMTUuNzE5MyAxOS4zMzg0QzE1LjgwMDUgMTkuMzg3MSAxNS44NjAyIDE5LjQ2MDIgMTUuODk4NSAxOS41NTc1QzE1LjkzNzkgMTkuNjU0OSAxNS45NTc2IDE5Ljc3NTUgMTUuOTU3NiAxOS45MTkyVjIxLjAwNzhIMTUuNTEyNFpNMTQuNDAxMiAyMS4wMDc4VjE5LjMwMTlIMTQuODQ2NEwxNC44MjkgMTkuNzE3NUwxNC44NDY0IDE5Ljc1NFYyMS4wMDc4SDE0LjQwMTJaTTE3LjE1ODMgMjEuMDQ0M0MxNy4wMTMzIDIxLjA0NDMgMTYuODk3NCAyMS4wMjI5IDE2LjgxMDUgMjAuOThDMTYuNzI0NyAyMC45MzU5IDE2LjY2MjcgMjAuODY5OSAxNi42MjQ0IDIwLjc4MThDMTYuNTg2MSAyMC42OTM2IDE2LjU2NyAyMC41ODUzIDE2LjU2NyAyMC40NTY2VjE5LjQ2MTlIMTcuMDA4N1YyMC4zOTA1QzE3LjAwODcgMjAuNDgzMiAxNy4wMjk2IDIwLjU1MTYgMTcuMDcxMyAyMC41OTU3QzE3LjExNDIgMjAuNjM4NiAxNy4xODkgMjAuNjYgMTcuMjk1NiAyMC42NkMxNy4zNTgyIDIwLjY2IDE3LjQxODUgMjAuNjUzNyAxNy40NzY1IDIwLjY0MDlDMTcuNTM0NCAyMC42MjcgMTcuNTg3OCAyMC42MDkgMTcuNjM2NSAyMC41ODdMMTcuNTk4MiAyMC45NTkxQzE3LjU0MDIgMjAuOTg1OCAxNy40NzM2IDIxLjAwNjcgMTcuMzk4MiAyMS4wMjE3QzE3LjMyNCAyMS4wMzY4IDE3LjI0NCAyMS4wNDQzIDE3LjE1ODMgMjEuMDQ0M1pNMTYuMzIwMSAxOS42NjcxVjE5LjMxNThIMTcuNjIwOEwxNy41ODI2IDE5LjY2NzFIMTYuMzIwMVpNMTYuNTcyMiAxOS4zNDg5TDE2LjU3MDUgMTguODk2OEwxNy4wMTM5IDE4Ljg1MTVMMTYuOTk2NSAxOS4zNDg5SDE2LjU3MjJaTTE4Ljc2MTUgMjEuMDUxM0MxOC40OTgzIDIxLjA1MTMgMTguMzAzIDIwLjk4MTcgMTguMTc1NSAyMC44NDI2QzE4LjA0OTEgMjAuNzAzNSAxNy45ODU5IDIwLjUwODIgMTcuOTg1OSAyMC4yNTY2VjIwLjA0NzlDMTcuOTg1OSAxOS43OTc1IDE4LjA0OTcgMTkuNjAzMyAxOC4xNzcyIDE5LjQ2NTRDMTguMzA0NyAxOS4zMjc0IDE4LjQ5OTUgMTkuMjU4NCAxOC43NjE1IDE5LjI1ODRDMTguODI5OSAxOS4yNTg0IDE4Ljg5MzcgMTkuMjY0MiAxOC45NTI4IDE5LjI3NThDMTkuMDEzMSAxOS4yODYzIDE5LjA2NzUgMTkuMzAwOCAxOS4xMTYyIDE5LjMxOTNDMTkuMTY2MSAxOS4zMzc5IDE5LjIwOTYgMTkuMzU3NiAxOS4yNDY3IDE5LjM3ODRMMTkuMjgzMiAxOS43NTIzQzE5LjIyNjQgMTkuNzE2NCAxOS4xNjI2IDE5LjY4NjIgMTkuMDkxOSAxOS42NjE5QzE5LjAyMjMgMTkuNjM3NSAxOC45NDE4IDE5LjYyNTQgMTguODUwMiAxOS42MjU0QzE4LjcwNjQgMTkuNjI1NCAxOC42MDA5IDE5LjY2MjUgMTguNTMzNyAxOS43MzY3QzE4LjQ2NzYgMTkuODA5NyAxOC40MzQ2IDE5LjkxNjMgMTguNDM0NiAyMC4wNTY2VjIwLjI0MDlDMTguNDM0NiAyMC4zODAxIDE4LjQ2ODggMjAuNDg3MyAxOC41MzcyIDIwLjU2MjZDMTguNjA2NyAyMC42MzggMTguNzEzNCAyMC42NzU3IDE4Ljg1NzEgMjAuNjc1N0MxOC45NDg3IDIwLjY3NTcgMTkuMDI5OSAyMC42NjQxIDE5LjEwMDYgMjAuNjQwOUMxOS4xNzEzIDIwLjYxNjYgMTkuMjM3NCAyMC41ODcgMTkuMjk4OCAyMC41NTIyTDE5LjI2MjMgMjAuOTI3OEMxOS4yMDU1IDIwLjk2MDMgMTkuMTM0MiAyMC45ODg3IDE5LjA0ODQgMjEuMDEzQzE4Ljk2MjYgMjEuMDM4NSAxOC44NjcgMjEuMDUxMyAxOC43NjE1IDIxLjA1MTNaTTE5Ljc2NjIgMjEuMDA3OFYxOC43MDg5SDIwLjIxMzFWMjEuMDA3OEgxOS43NjYyWk0yMC43ODU4IDIxLjAwNzhWMTkuMzAxOUgyMS4yMzA5VjIxLjAwNzhIMjAuNzg1OFpNMjEuMDA4NCAxOS4xMDAyQzIwLjkyMzcgMTkuMTAwMiAyMC44NjExIDE5LjA4MDUgMjAuODIwNSAxOS4wNDExQzIwLjc4MTEgMTkuMDAwNSAyMC43NjE0IDE4Ljk0NDkgMjAuNzYxNCAxOC44NzQxVjE4Ljg2NTRDMjAuNzYxNCAxOC43OTQ3IDIwLjc4MTEgMTguNzM5MSAyMC44MjA1IDE4LjY5ODVDMjAuODYxMSAxOC42NTc5IDIwLjkyMzcgMTguNjM3NiAyMS4wMDg0IDE4LjYzNzZDMjEuMDkxOCAxOC42Mzc2IDIxLjE1MzggMTguNjU3OSAyMS4xOTQ0IDE4LjY5ODVDMjEuMjM1IDE4LjczOTEgMjEuMjU1MyAxOC43OTQ3IDIxLjI1NTMgMTguODY1NFYxOC44NzQxQzIxLjI1NTMgMTguOTQ2IDIxLjIzNSAxOS4wMDE3IDIxLjE5NDQgMTkuMDQxMUMyMS4xNTM4IDE5LjA4MDUgMjEuMDkxOCAxOS4xMDAyIDIxLjAwODQgMTkuMTAwMlpNMjIuMzk1IDE4LjY1NUMyMi40ODU0IDE4LjY1NSAyMi41Njg5IDE4LjY2MjYgMjIuNjQ1NCAxOC42Nzc2QzIyLjcyMTkgMTguNjkyNyAyMi43ODk4IDE4LjcxMTMgMjIuODQ4OSAxOC43MzMzTDIyLjg4ODkgMTkuMDY3MkMyMi44MzkgMTkuMDUyMSAyMi43ODY5IDE5LjAzOTkgMjIuNzMyNCAxOS4wMzA2QzIyLjY3OSAxOS4wMjAyIDIyLjYxOTkgMTkuMDE1IDIyLjU1NSAxOS4wMTVDMjIuNDc3MyAxOS4wMTUgMjIuNDE1OSAxOS4wMjM3IDIyLjM3MDcgMTkuMDQxMUMyMi4zMjY2IDE5LjA1ODUgMjIuMjk1MyAxOS4wODM0IDIyLjI3NjggMTkuMTE1OUMyMi4yNTgyIDE5LjE0ODMgMjIuMjQ4OSAxOS4xODY2IDIyLjI0ODkgMTkuMjMwNlYxOS4yMzU4QzIyLjI0ODkgMTkuMjY3MSAyMi4yNTM2IDE5LjI5NjcgMjIuMjYyOSAxOS4zMjQ1QzIyLjI3MjEgMTkuMzUyNCAyMi4yODMxIDE5LjM3NzMgMjIuMjk1OSAxOS4zOTkzTDIyLjAwNTUgMTkuNDA5N1YxOS4zNjI4QzIxLjk1MjIgMTkuMzM3MyAyMS45MDcgMTkuMjk5NiAyMS44Njk5IDE5LjI0OThDMjEuODMyOCAxOS4xOTk5IDIxLjgxNDIgMTkuMTM3OSAyMS44MTQyIDE5LjA2MzdWMTkuMDU1QzIxLjgxNDIgMTguOTMyMSAyMS44NjEyIDE4LjgzNDcgMjEuOTU1MSAxOC43NjI5QzIyLjA1MDEgMTguNjkxIDIyLjE5NjggMTguNjU1IDIyLjM5NSAxOC42NTVaTTIxLjg3MzMgMjEuMDA3OFYxOS41MDE5SDIyLjMxNjhWMjEuMDA3OEgyMS44NzMzWk0yMS42MjY0IDE5LjcyOFYxOS4zNzVMMjIuMDU3NyAxOS4zNzg0TDIyLjIzMTYgMTkuMzc1SDIyLjg4NTRMMjIuODQ3MSAxOS43MjhIMjEuNjI2NFpNMjMuNTExNCAxOC42NTVDMjMuNjAxOCAxOC42NTUgMjMuNjg1MyAxOC42NjI2IDIzLjc2MTggMTguNjc3NkMyMy44MzgzIDE4LjY5MjcgMjMuOTA2MSAxOC43MTEzIDIzLjk2NTMgMTguNzMzM0wyNC4wMDUzIDE5LjA2NzJDMjMuOTU1NCAxOS4wNTIxIDIzLjkwMzIgMTkuMDM5OSAyMy44NDg4IDE5LjAzMDZDMjMuNzk1NCAxOS4wMjAyIDIzLjczNjMgMTkuMDE1IDIzLjY3MTQgMTkuMDE1QzIzLjU5MzcgMTkuMDE1IDIzLjUzMjMgMTkuMDIzNyAyMy40ODcxIDE5LjA0MTFDMjMuNDQzIDE5LjA1ODUgMjMuNDExNyAxOS4wODM0IDIzLjM5MzIgMTkuMTE1OUMyMy4zNzQ2IDE5LjE0ODMgMjMuMzY1MyAxOS4xODY2IDIzLjM2NTMgMTkuMjMwNlYxOS4yMzU4QzIzLjM2NTMgMTkuMjY3MSAyMy4zNyAxOS4yOTY3IDIzLjM3OTMgMTkuMzI0NUMyMy4zODg1IDE5LjM1MjQgMjMuMzk5NSAxOS4zNzczIDIzLjQxMjMgMTkuMzk5M0wyMy4xMjE5IDE5LjQwOTdWMTkuMzYyOEMyMy4wNjg2IDE5LjMzNzMgMjMuMDIzMyAxOS4yOTk2IDIyLjk4NjMgMTkuMjQ5OEMyMi45NDkyIDE5LjE5OTkgMjIuOTMwNiAxOS4xMzc5IDIyLjkzMDYgMTkuMDYzN1YxOS4wNTVDMjIuOTMwNiAxOC45MzIxIDIyLjk3NzYgMTguODM0NyAyMy4wNzE1IDE4Ljc2MjlDMjMuMTY2NSAxOC42OTEgMjMuMzEzMiAxOC42NTUgMjMuNTExNCAxOC42NTVaTTIyLjk4OTcgMjEuMDA3OFYxOS41MDE5SDIzLjQzMzJWMjEuMDA3OEgyMi45ODk3Wk0yMi43NDI4IDE5LjcyOFYxOS4zNzVMMjMuMTc0MSAxOS4zNzg0TDIzLjM0OCAxOS4zNzVIMjQuMDAxOEwyMy45NjM1IDE5LjcyOEgyMi43NDI4WiIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTEyLjgyOTkgMjQuODQ0MlYyNC4yNzg4SDEzLjQzMkMxMy42MDQ2IDI0LjI3ODggMTMuNzM0MSAyNC4yMzIgMTMuODIwNSAyNC4xMzg1QzEzLjkwNjggMjQuMDQ1IDEzLjk1IDIzLjkxMTIgMTMuOTUgMjMuNzM3MVYyMy4xODQ2QzEzLjk1IDIzLjAxMDUgMTMuOTA2OCAyMi44Nzc0IDEzLjgyMDUgMjIuNzg1M0MxMy43MzQxIDIyLjY5MTggMTMuNjA0NiAyMi42NDUxIDEzLjQzMiAyMi42NDUxSDEyLjgyNzdWMjIuMDg2MUgxMy40NTU3QzEzLjg0ODUgMjIuMDg2MSAxNC4xNDIgMjIuMTgxIDE0LjMzNjMgMjIuMzcxQzE0LjUzMTkgMjIuNTU5NCAxNC42Mjk4IDIyLjgzMDcgMTQuNjI5OCAyMy4xODQ2VjIzLjczOTJDMTQuNjI5OCAyNC4wOTQ2IDE0LjUzMjcgMjQuMzY4IDE0LjMzODQgMjQuNTU5NEMxNC4xNDQyIDI0Ljc0OTMgMTMuODUgMjQuODQ0MiAxMy40NTU3IDI0Ljg0NDJIMTIuODI5OVpNMTIuMzU3MiAyNC44NDQyVjIyLjA4NjFIMTMuMDIxOVYyNC44NDQySDEyLjM1NzJaTTE1LjIxNTMgMjQuODQ0MkwxNS4zMDE2IDIyLjA4NjFIMTYuMjQyNkwxNi42OTc5IDIzLjczMDZIMTYuNzI2TDE3LjE4MTQgMjIuMDg2MUgxOC4xMjIzTDE4LjIwODcgMjQuODQ0MkgxNy41NTQ3TDE3LjUzNTMgMjMuOTRMMTcuNTE1OSAyMi44MzkzSDE3LjQ4MzVMMTcuMDEwOSAyNC41MjA1SDE2LjQxMDlMMTUuOTM4MiAyMi44MzkzSDE1LjkwMzdMMTUuODg2NSAyMy45NDIxTDE1Ljg2OTIgMjQuODQ0MkgxNS4yMTUzWk0xOS40MDA3IDI0Ljg0NDJMMTguNjQ1NCAyMi4wODYxSDE5LjM0NDZMMTkuODg2MyAyNC4zNDE0SDE5LjkzNkwyMC40Nzk4IDIyLjA4NjFIMjEuMTc2OUwyMC40MjM3IDI0Ljg0NDJIMTkuNDAwN1oiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTE2LjY2OTcgMi43MjMxNEwxNi4zNTgxIDMuMzUyODNMMTUuNjYyOCAzLjQ1NDVMMTYuMTY0NiAzLjk0NjQ0TDE2LjA0NjYgNC42NDE3M0wxNi42Njk3IDQuMzEzNzZMMTcuMjkyOCA0LjY0MTczTDE3LjE3NDcgMy45NDY0NEwxNy42NzY1IDMuNDU0NUwxNi45ODEzIDMuMzUyODNMMTYuNjY5NyAyLjcyMzE0WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjEuMjExOSAzLjU0MzQ2TDIwLjcwMzUgNC4wMjg4NEwyMC4wMTQ4IDMuODg0NTRMMjAuMzE5OCA0LjUyMDc5TDE5Ljk3MjIgNS4xMzA4TDIwLjY2NzQgNS4wMzU2OUwyMS4xNDMgNS41NTcxNUwyMS4yNjc2IDQuODY1MTVMMjEuOTEwNCA0LjU3NjU0TDIxLjI5MDYgNC4yNDIwMkwyMS4yMTE5IDMuNTQzNDZaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0yNS4yMDMyIDUuODY1NTRMMjQuNTU3MSA2LjE1MDg3TDIzLjk2MDIgNS43ODAyN0wyNC4wMjkxIDYuNDc4ODNMMjMuNDkxMiA2LjkzNDdMMjQuMTc5OSA3LjA4NTU2TDI0LjQ0NTYgNy43MzQ5M0wyNC43OTk4IDcuMTI4MkwyNS41MDE2IDcuMDc1NzNMMjUuMDM1OSA2LjU1MDk5TDI1LjIwMzIgNS44NjU1NFoiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTI4LjE1NDkgOS40MTM3NUwyNy40NTMxIDkuNDU5NjdMMjcuMDE2OSA4LjkwODY5TDI2Ljg0MyA5LjU5MDg1TDI2LjE4MzggOS44MzM1NUwyNi43Nzc0IDEwLjIwNzRMMjYuODA3IDEwLjkxMjVMMjcuMzQ4MSAxMC40NjMyTDI4LjAyMzcgMTAuNjUzNUwyNy43NjQ2IDEwLjAwMDhMMjguMTU0OSA5LjQxMzc1WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjkuNzE1OSAxMy43NTk0TDI5LjA0MzYgMTMuNTYyNkwyOC44MjA2IDEyLjg5MzZMMjguNDIzOCAxMy40NzRMMjcuNzIxOSAxMy40NzczTDI4LjE1MTYgMTQuMDM0OUwyNy45Mzg0IDE0LjcwMzlMMjguNjAwOSAxNC40Njc4TDI5LjE3MTUgMTQuODc3N0wyOS4xNTE4IDE0LjE3NTlMMjkuNzE1OSAxMy43NTk0WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjkuNjk5NyAxOC4zNzM3TDI5LjEzMjMgMTcuOTU3MkwyOS4xNTUzIDE3LjI1NTRMMjguNTgxMyAxNy42NjUzTDI3LjkxODggMTcuNDI5MkwyOC4xMzUzIDE4LjA5ODJMMjcuNzAyNCAxOC42NTU4TDI4LjQwNzUgMTguNjU5MUwyOC44MDQzIDE5LjIzOTVMMjkuMDI0MSAxOC41NzA1TDI5LjY5OTcgMTguMzczN1oiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTI4LjEwMjQgMjIuNzA2M0wyNy43MTIxIDIyLjEyMjVMMjcuOTc0NSAyMS40NjY2TDI3LjI5NTYgMjEuNjYwMUwyNi43NTQ1IDIxLjIwNzVMMjYuNzI4MiAyMS45MTI2TDI2LjEzMTMgMjIuMjg2NUwyNi43OTM4IDIyLjUzMjVMMjYuOTY0NCAyMy4yMTQ2TDI3LjQwMDYgMjIuNjYwNEwyOC4xMDI0IDIyLjcwNjNaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0yNS4xMjQ2IDI2LjIzMTdMMjQuOTU3MyAyNS41NDk1TDI1LjQyMyAyNS4wMjE1TDI0LjcyMTIgMjQuOTY5TDI0LjM2NyAyNC4zNjIzTDI0LjEwMTMgMjUuMDE0OUwyMy40MTI2IDI1LjE2MjVMMjMuOTUwNSAyNS42MTg0TDIzLjg4MTYgMjYuMzIwMkwyNC40Nzg1IDI1Ljk0OTZMMjUuMTI0NiAyNi4yMzE3WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjEuMTE3IDI4LjUyNDRMMjEuMTkyNCAyNy44MjU5TDIxLjgxMjMgMjcuNDkxM0wyMS4xNzI4IDI3LjIwMjdMMjEuMDQ0OCAyNi41MTA3TDIwLjU3MjYgMjcuMDMyMkwxOS44NzQgMjYuOTM3MUwyMC4yMjQ5IDI3LjU0NzFMMTkuOTE5OSAyOC4xODAxTDIwLjYwODcgMjguMDM5TDIxLjExNyAyOC41MjQ0WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMTYuNTY4IDI5LjMwNzlMMTYuODc5NiAyOC42NzgyTDE3LjU3NDggMjguNTc2NUwxNy4wNjk4IDI4LjA4NDZMMTcuMTkxMSAyNy4zOTI2TDE2LjU2OCAyNy43MjA1TDE1Ljk0NDkgMjcuMzkyNkwxNi4wNjI5IDI4LjA4NDZMMTUuNTU3OSAyOC41NzY1TDE2LjI1NjQgMjguNjc4MkwxNi41NjggMjkuMzA3OVoiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTEyLjAyMjQgMjguNDg4M0wxMi41MzA4IDI4LjAwMjlMMTMuMjE5NSAyOC4xNDcyTDEyLjkxNDUgMjcuNTExTDEzLjI2NTQgMjYuOTAxTDEyLjU2NjggMjYuOTk2MUwxMi4wOTQ2IDI2LjQ3NDZMMTEuOTY2NyAyNy4xNjY2TDExLjMyNzEgMjcuNDU4NUwxMS45NDcgMjcuNzg5N0wxMi4wMjI0IDI4LjQ4ODNaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik04LjAzNDM5IDI2LjE2NjNMOC42NzcyIDI1Ljg4NDJMOS4yNzczNyAyNi4yNTE1TDkuMjA1MjIgMjUuNTUzTDkuNzQzMDggMjUuMDk3MUw5LjA1NDM1IDI0Ljk0OTVMOC43ODg3IDI0LjI5NjlMOC40MzQ1MSAyNC45MDM2TDcuNzMyNjcgMjQuOTU2MUw4LjIwMTY1IDI1LjQ4MDhMOC4wMzQzOSAyNi4xNjYzWiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNNS4wNzk1OSAyMi42MTc0TDUuNzgxNDMgMjIuNTcxNUw2LjIxNzYyIDIzLjEyNThMNi4zOTE0NCAyMi40NDM2TDcuMDUwNjQgMjIuMTk3Nkw2LjQ1NzAzIDIxLjgyMzhMNi40MzA3OSAyMS4xMTg3TDUuODg5NjYgMjEuNTY4TDUuMjEwNzcgMjEuMzc3N0w1LjQ2OTg2IDIyLjAzMzdMNS4wNzk1OSAyMi42MTc0WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMy41MTgzMSAxOC4yNzIyTDQuMTkzOTEgMTguNDcyMkw0LjQxMzY1IDE5LjEzOEw0LjgxMDQ4IDE4LjU1NzVMNS41MTU2IDE4LjU1NDJMNS4wODI2OSAxNy45OTY3TDUuMjk5MTUgMTcuMzI3Nkw0LjYzNjY2IDE3LjU2MzhMNC4wNjI3MyAxNy4xNTM4TDQuMDg1NjkgMTcuODU4OUwzLjUxODMxIDE4LjI3MjJaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zLjUzNDkxIDEzLjY1NzhMNC4xMDIyOSAxNC4wNzQzTDQuMDgyNjEgMTQuNzc2Mkw0LjY1MzI2IDE0LjM2NjJMNS4zMTU3NSAxNC42MDIzTDUuMTAyNTcgMTMuOTMzM0w1LjUzMjIgMTMuMzc1OEw0LjgyNzA4IDEzLjM3MjVMNC40MzM1MyAxMi43OTJMNC4yMTA1MSAxMy40NjFMMy41MzQ5MSAxMy42NTc4WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNNS4xMzIwOCA5LjMyNTI0TDUuNTIyMzYgOS45MDkwMUw1LjI2MzI3IDEwLjU2NDlMNS45Mzg4NyAxMC4zNzQ3TDYuNDgwMDEgMTAuODI0TDYuNTA5NTIgMTAuMTE4OUw3LjEwMzEzIDkuNzQ1MDNMNi40NDM5MyA5LjQ5OTA2TDYuMjcwMTEgOC44MTY4OUw1LjgzMzkyIDkuMzcxMTVMNS4xMzIwOCA5LjMyNTI0WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNOC4xMTMyNSA1Ljc5OTYzTDguMjgwNTEgNi40ODUwN0w3LjgxMTUyIDcuMDA5ODFMOC41MTMzNiA3LjA2MjI4TDguODY3NTYgNy42NjkwMUw5LjEzNjQ5IDcuMDE2MzdMOS44MjE5MyA2Ljg2ODc4TDkuMjg0MDcgNi40MTI5Mkw5LjM1NjIzIDUuNzE0MzZMOC43NTYwNiA2LjA4MTY3TDguMTEzMjUgNS43OTk2M1oiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTEyLjEyMDkgMy41MDczMkwxMi4wNDIyIDQuMjA1ODhMMTEuNDIyNCA0LjU0MDQxTDEyLjA2NTIgNC44MjkwMUwxMi4xODk4IDUuNTIxMDFMMTIuNjY1MyA0Ljk5OTU1TDEzLjM2MDYgNS4wOTQ2NkwxMy4wMTMgNC40ODQ2NUwxMy4zMTggMy44NTE2OEwxMi42MjkzIDMuOTkyNzFMMTIuMTIwOSAzLjUwNzMyWiIgZmlsbD0iI0RDODU0OCIvPgo8L3N2Zz4Kamlzc3VlckxvZ2+iZmZvcm1hdGNzdmdkZGF0YVmBojxzdmcgd2lkdGg9IjE0NSIgaGVpZ2h0PSI0MiIgdmlld0JveD0iMCAwIDE0NSA0MiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTIwLjk2OTkgNDEuOTdDMzIuNTUxMiA0MS45NyA0MS45Mzk3IDMyLjU4MTUgNDEuOTM5NyAyMS4wMDAxQzQxLjkzOTcgOS40MTg4IDMyLjU1MTIgMC4wMzAyNzM0IDIwLjk2OTkgMC4wMzAyNzM0QzkuMzg4NTMgMC4wMzAyNzM0IDAgOS40MTg4IDAgMjEuMDAwMUMwIDMyLjU4MTUgOS4zODg1MyA0MS45NyAyMC45Njk5IDQxLjk3WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjAuOTA1MyAzOS42OTU0QzMxLjIxOTIgMzkuNjk1NCAzOS41ODAyIDMxLjMzNDQgMzkuNTgwMiAyMS4wMjA2QzM5LjU4MDIgMTAuNzA2NyAzMS4yMTkyIDIuMzQ1NyAyMC45MDUzIDIuMzQ1N0MxMC41OTE1IDIuMzQ1NyAyLjIzMDQ3IDEwLjcwNjcgMi4yMzA0NyAyMS4wMjA2QzIuMjMwNDcgMzEuMzM0NCAxMC41OTE1IDM5LjY5NTQgMjAuOTA1MyAzOS42OTU0WiIgZmlsbD0iIzAwMzQ1OSIvPgo8cGF0aCBkPSJNMjAuOTA1NCAxMy42NzA5TDMyLjMyNzUgMTQuMzA4OUwyMC45MDU0IDE0Ljk0N0w5LjQ4MzQgMTQuMzA4OUwyMC45MDU0IDEzLjY3MDlaIiBmaWxsPSIjMEE0Qjc3Ii8+CjxwYXRoIGQ9Ik0yMC45MDU0IDI4LjE5NjNMOS40ODMzNCAyNy41NTgyTDIwLjkwNTQgMjYuOTIwMkwzMi4zMjc0IDI3LjU1ODJMMjAuOTA1NCAyOC4xOTYzWiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMjAuOTA1MyAxMS42MTUyTDMwLjA2MjggMTIuMjUzM0wyMC45MDUzIDEyLjg5MTNMMTEuNzQ3OCAxMi4yNTMzTDIwLjkwNTMgMTEuNjE1MloiIGZpbGw9IiMwQTRCNzciLz4KPHBhdGggZD0iTTIwLjkwNTIgMzAuMjUyTDExLjc0NzggMjkuNjEzOUwyMC45MDUyIDI4Ljk3NTlMMzAuMDYyNyAyOS42MTM5TDIwLjkwNTIgMzAuMjUyWiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMjAuOTA1MyA5LjU2MDA2TDI3LjQ4NDUgMTAuMTk4MUwyMC45MDUzIDEwLjgzNjFMMTQuMzI2MiAxMC4xOTgxTDIwLjkwNTMgOS41NjAwNloiIGZpbGw9IiMwQTRCNzciLz4KPHBhdGggZD0iTTIwLjkwNTIgMzIuMzA3MUwxNC4zMjYgMzEuNjY5MUwyMC45MDUyIDMxLjAzMUwyNy40ODQ0IDMxLjY2OTFMMjAuOTA1MiAzMi4zMDcxWiIgZmlsbD0iIzBBNEI3NyIvPgo8cGF0aCBkPSJNMzguNzYzOCAyMC43MTQ5QzM4Ljc2MzggMzAuNTc1IDMwLjc2NjIgMzguNTY3OCAyMC45MDYxIDM4LjU2NzhDMTEuMDQ2IDM4LjU2NzggMy4wNTMyMiAzMC41NzUgMy4wNTMyMiAyMC43MTQ5QzMuMDUzMjIgMjAuMDMzMyAzLjA5MTYzIDE5LjM2MTIgMy4xNjg0MyAxOC42OTg3QzQuMTcxNzMgMjcuNjA4NCAxMS43Mjc2IDM0LjUzMDYgMjAuOTA2MSAzNC41MzA2QzMwLjA4NDYgMzQuNTMwNiAzNy42NDUzIDI3LjYwODQgMzguNjQ4NiAxOC42OTg3QzM4LjcyNTQgMTkuMzYxMiAzOC43NjM4IDIwLjAzMzMgMzguNzYzOCAyMC43MTQ5WiIgZmlsbD0iIzAwMkQ0RCIvPgo8cGF0aCBkPSJNMTcuNzQ5NyAyMS4xNzc0TDE0LjY4NzQgMTguNzA2M0MxNC42MDMyIDE4LjYzODYgMTQuNTU0MiAxOC41MzY0IDE0LjU1NDIgMTguNDI4M0wxNC41NTQyIDE2LjUxMjZDMTQuNTU0MiAxNi4zNzM2IDE0LjYzNTIgMTYuMjQ2OSAxNC43NjE0IDE2LjE4ODNMMTcuMzI0NyAxNC45OTk2QzE3LjUxIDE0LjkxMzggMTcuNTg1NiAxNC42OTAxIDE3LjQ5MDggMTQuNTA5MUwxNi42Mzk4IDEyLjg4ODVDMTYuNjEyNiAxMi44MzczIDE2LjU5ODggMTIuNzgwMyAxNi41OTg4IDEyLjcyMjNMMTYuNTk4OCAxMS44NzIzQzE2LjU5ODggMTEuNzQ2MSAxNi42NjU5IDExLjYyODkgMTYuNzc0NSAxMS41NjQ1TDIyLjAxMjkgOC40NzI4OUMyMi4xNDEzIDguMzk3MjcgMjIuMzAzMiA4LjQwODk4IDIyLjQxOTMgOC41MDI3MkwyNy4zODU1IDEyLjUxNDFMMjcuMzg1NSAxNS4yNjUzQzI3LjM4NTUgMTUuNDYyOSAyNy4yMjU3IDE1LjYyMjcgMjcuMDI4MSAxNS42MjI3TDI0LjIwNTYgMTUuNzk4M0MyNC4wMDggMTUuNzk4MyAyMy44NDgyIDE1Ljk1OCAyMy44NDgyIDE2LjE1NTZMMjMuNjE1NCAxOC44MTA3QzIzLjYxNTQgMTkuMDA4MyAyMy43NzUyIDE5LjE2OCAyMy45NzI4IDE5LjE2OEMyNC4xNzA0IDE5LjE2OCAyNC4zMzAxIDE5LjMyNzggMjQuMzMwMSAxOS41MjU0TDI0LjMzMDEgMjEuOTQ1NEMyNC4zMzAxIDIyLjE0MyAyNC4xNzA0IDIyLjMwMjggMjMuOTcyOCAyMi4zMDI4TDIxLjU1MTIgMjIuMzAyOEMyMS41MTY1IDIyLjMwMjggMjEuNDgxOSAyMi4yOTc0IDIxLjQ0ODQgMjIuMjg3OEwxNy43NDkxIDIxLjE3NzQiIGZpbGw9IiMwMEE4RTgiLz4KPHBhdGggZD0iTTI1LjY4NzkgMTkuNDI1NUwyNi40NjggMTguNTQ1OUMyNi41MDM4IDE4LjUwNTQgMjYuNTE5OSAxOC40NTExIDI2LjUxMTkgMTguMzk3NkwyNi4yNzE3IDE2LjgwMDVDMjYuMjYwOSAxNi43Mjg3IDI2LjIwODYgMTYuNjcwMiAyNi4xMzg1IDE2LjY1MTRMMjUuMzU1OSAxNi40NDE3QzI1LjI1ODYgMTYuNDE1NiAyNS4xNTg1IDE2LjQ3MzQgMjUuMTMyNCAxNi41NzA3TDI0LjYxOCAxOC40OTA3QzI0LjU5MTkgMTguNTg4MSAyNC42NDk3IDE4LjY4ODEgMjQuNzQ3IDE4LjcxNDJMMjUuMTY0MiAxOC44MjZDMjUuMjMxIDE4Ljg0MzkgMjUuMjgxOSAxOC44OTggMjUuMjk1OCAxOC45NjU3TDI1LjM3MjYgMTkuMzQxQzI1LjQwMjcgMTkuNDg4MyAyNS41ODgxIDE5LjUzOCAyNS42ODc5IDE5LjQyNTVaIiBmaWxsPSIjMDBBOEU4Ii8+CjxwYXRoIGQ9Ik0xMS42NTA2IDI3LjMwMjdMMTEuNzQ1NCAyNC40MTY2SDEyLjU5OUwxMy4xNDEgMjYuMjUyNkgxMy4xNzA0TDEzLjcxMDEgMjQuNDE2NkgxNC41NjE1TDE0LjY1ODYgMjcuMzAyN0gxNC4wODVMMTQuMDU3OSAyNi4yNjM5TDE0LjAzMzEgMjUuMDY0OEgxMy45OTY5TDEzLjQzMDEgMjYuOTY2MkgxMi44NzkxTDEyLjMxIDI1LjA2NDhIMTIuMjczOEwxMi4yNDkgMjYuMjY2MkwxMi4yMjQyIDI3LjMwMjdIMTEuNjUwNlpNMTYuMzM4NCAyNy4zNjM3QzE1Ljk5NTEgMjcuMzYzNyAxNS43MzQ3IDI3LjI3NTYgMTUuNTU3IDI3LjA5OTVDMTUuMzgwOSAyNi45MjMzIDE1LjI5MjggMjYuNjcyNyAxNS4yOTI4IDI2LjM0NzVWMjYuMDQ3MUMxNS4yOTI4IDI1LjcyMDQgMTUuMzgwOSAyNS40NjkgMTUuNTU3IDI1LjI5MjlDMTUuNzM0NyAyNS4xMTUyIDE1Ljk5NTEgMjUuMDI2NCAxNi4zMzg0IDI1LjAyNjRDMTYuNjgwMSAyNS4wMjY0IDE2LjkzOTEgMjUuMTE1MiAxNy4xMTUyIDI1LjI5MjlDMTcuMjkxNCAyNS40NjkgMTcuMzc5NSAyNS43MjA0IDE3LjM3OTUgMjYuMDQ3MVYyNi4zNDc1QzE3LjM3OTUgMjYuNjcyNyAxNy4yOTE0IDI2LjkyMzMgMTcuMTE1MiAyNy4wOTk1QzE2Ljk0MDYgMjcuMjc1NiAxNi42ODE2IDI3LjM2MzcgMTYuMzM4NCAyNy4zNjM3Wk0xNi4zMzg0IDI2LjkwMDhDMTYuNDg4OSAyNi45MDA4IDE2LjYwMzQgMjYuODU1NiAxNi42ODE2IDI2Ljc2NTNDMTYuNzYxNCAyNi42NzQ5IDE2LjgwMTMgMjYuNTQ1NSAxNi44MDEzIDI2LjM3NjhWMjYuMDE3OEMxNi44MDEzIDI1Ljg0NjEgMTYuNzYxNCAyNS43MTUyIDE2LjY4MTYgMjUuNjI0OEMxNi42MDM0IDI1LjUzMyAxNi40ODg5IDI1LjQ4NzEgMTYuMzM4NCAyNS40ODcxQzE2LjE4NjMgMjUuNDg3MSAxNi4wNzA0IDI1LjUzMyAxNS45OTA2IDI1LjYyNDhDMTUuOTEyMyAyNS43MTUyIDE1Ljg3MzIgMjUuODQ2MSAxNS44NzMyIDI2LjAxNzhWMjYuMzc2OEMxNS44NzMyIDI2LjU0NTUgMTUuOTEyMyAyNi42NzQ5IDE1Ljk5MDYgMjYuNzY1M0MxNi4wNzA0IDI2Ljg1NTYgMTYuMTg2MyAyNi45MDA4IDE2LjMzODQgMjYuOTAwOFpNMTkuNDcxMSAyNy4zMDI3VjI1Ljk3MjZDMTkuNDcxMSAyNS44NzkzIDE5LjQ1ODMgMjUuNzk4NyAxOS40MzI3IDI1LjczMUMxOS40MDg3IDI1LjY2MzIgMTkuMzY4IDI1LjYxMDUgMTkuMzEwOCAyNS41NzI5QzE5LjI1MzYgMjUuNTM1MiAxOS4xNzUzIDI1LjUxNjQgMTkuMDc1OSAyNS41MTY0QzE4Ljk4ODYgMjUuNTE2NCAxOC45MTE4IDI1LjUzMjIgMTguODQ1NiAyNS41NjM4QzE4Ljc4MDggMjUuNTk1NSAxOC43Mjc0IDI1LjYzODQgMTguNjg1MiAyNS42OTI2QzE4LjY0NDYgMjUuNzQ1MyAxOC42MTM3IDI1LjgwNTUgMTguNTkyNyAyNS44NzMyTDE4LjUwMjMgMjUuNTU3MUgxOC42MTA3QzE4LjYzNDggMjUuNDU5MiAxOC42NzQ3IDI1LjM3MTEgMTguNzMwNCAyNS4yOTI5QzE4Ljc4NzYgMjUuMjE0NiAxOC44NjQ0IDI1LjE1MjggMTguOTYwOCAyNS4xMDc3QzE5LjA1ODYgMjUuMDYxIDE5LjE4MDYgMjUuMDM3NyAxOS4zMjY2IDI1LjAzNzdDMTkuNDk2NyAyNS4wMzc3IDE5LjYzNDUgMjUuMDcgMTkuNzM5OSAyNS4xMzQ4QzE5Ljg0NTMgMjUuMTk4IDE5LjkyMjggMjUuMjkyOSAxOS45NzI1IDI1LjQxOTNDMjAuMDIzNyAyNS41NDU4IDIwLjA0OTMgMjUuNzAyNCAyMC4wNDkzIDI1Ljg4OVYyNy4zMDI3SDE5LjQ3MTFaTTE4LjAyODEgMjcuMzAyN1YyNS4wODczSDE4LjYwNjJMMTguNTgzNiAyNS42MjcxTDE4LjYwNjIgMjUuNjc0NVYyNy4zMDI3SDE4LjAyODFaTTIxLjYwODUgMjcuMzUwMkMyMS40MjAzIDI3LjM1MDIgMjEuMjY5OCAyNy4zMjIzIDIxLjE1NjkgMjcuMjY2NkMyMS4wNDU1IDI3LjIwOTQgMjAuOTY0OSAyNy4xMjM2IDIwLjkxNTIgMjcuMDA5MkMyMC44NjU2IDI2Ljg5NDcgMjAuODQwNyAyNi43NTQgMjAuODQwNyAyNi41ODY5VjI1LjI5NTFIMjEuNDE0M1YyNi41MDFDMjEuNDE0MyAyNi42MjE1IDIxLjQ0MTQgMjYuNzEwMyAyMS40OTU2IDI2Ljc2NzVDMjEuNTUxMyAyNi44MjMyIDIxLjY0ODQgMjYuODUxMSAyMS43ODY5IDI2Ljg1MTFDMjEuODY4MiAyNi44NTExIDIxLjk0NjUgMjYuODQyOCAyMi4wMjE4IDI2LjgyNjJDMjIuMDk3MSAyNi44MDgyIDIyLjE2NjMgMjYuNzg0OCAyMi4yMjk2IDI2Ljc1NjJMMjIuMTc5OSAyNy4yMzk1QzIyLjEwNDYgMjcuMjc0MSAyMi4wMTggMjcuMzAxMiAyMS45MjAyIDI3LjMyMDhDMjEuODIzOCAyNy4zNDA0IDIxLjcxOTkgMjcuMzUwMiAyMS42MDg1IDI3LjM1MDJaTTIwLjUyIDI1LjU2MTZWMjUuMTA1NEgyMi4yMDkyTDIyLjE1OTYgMjUuNTYxNkgyMC41MlpNMjAuODQ3NSAyNS4xNDgzTDIwLjg0NTIgMjQuNTYxMkwyMS40MjExIDI0LjUwMjVMMjEuMzk4NSAyNS4xNDgzSDIwLjg0NzVaTTIzLjY5MDYgMjcuMzU5MkMyMy4zNDg5IDI3LjM1OTIgMjMuMDk1MiAyNy4yNjg5IDIyLjkyOTYgMjcuMDg4MkMyMi43NjU1IDI2LjkwNzUgMjIuNjgzNCAyNi42NTM5IDIyLjY4MzQgMjYuMzI3MlYyNi4wNTYyQzIyLjY4MzQgMjUuNzMxIDIyLjc2NjIgMjUuNDc4OCAyMi45MzE4IDI1LjI5OTZDMjMuMDk3NCAyNS4xMjA1IDIzLjM1MDQgMjUuMDMwOSAyMy42OTA2IDI1LjAzMDlDMjMuNzc5NCAyNS4wMzA5IDIzLjg2MjIgMjUuMDM4NCAyMy45MzkgMjUuMDUzNUMyNC4wMTczIDI1LjA2NyAyNC4wODgxIDI1LjA4NTggMjQuMTUxMyAyNS4xMDk5QzI0LjIxNiAyNS4xMzQgMjQuMjcyNSAyNS4xNTk2IDI0LjMyMDcgMjUuMTg2N0wyNC4zNjgxIDI1LjY3MjJDMjQuMjk0MyAyNS42MjU2IDI0LjIxMTUgMjUuNTg2NCAyNC4xMTk3IDI1LjU1NDhDMjQuMDI5NCAyNS41MjMyIDIzLjkyNDcgMjUuNTA3NCAyMy44MDU4IDI1LjUwNzRDMjMuNjE5MSAyNS41MDc0IDIzLjQ4MjEgMjUuNTU1NiAyMy4zOTQ4IDI1LjY1MTlDMjMuMzA5IDI1Ljc0NjggMjMuMjY2MSAyNS44ODUzIDIzLjI2NjEgMjYuMDY3NFYyNi4zMDY4QzIzLjI2NjEgMjYuNDg3NSAyMy4zMTA1IDI2LjYyNjggMjMuMzk5MyAyNi43MjQ2QzIzLjQ4OTYgMjYuODIyNSAyMy42MjgxIDI2Ljg3MTQgMjMuODE0OCAyNi44NzE0QzIzLjkzMzggMjYuODcxNCAyNC4wMzkxIDI2Ljg1NjMgMjQuMTMxIDI2LjgyNjJDMjQuMjIyOCAyNi43OTQ2IDI0LjMwODYgMjYuNzU2MiAyNC4zODg0IDI2LjcxMTFMMjQuMzQxIDI3LjE5ODlDMjQuMjY3MiAyNy4yNDEgMjQuMTc0NiAyNy4yNzc5IDI0LjA2MzIgMjcuMzA5NUMyMy45NTE4IDI3LjM0MjYgMjMuODI3NiAyNy4zNTkyIDIzLjY5MDYgMjcuMzU5MlpNMjQuOTk1NCAyNy4zMDI3VjI0LjMxNzNIMjUuNTc1OFYyNy4zMDI3SDI0Ljk5NTRaTTI2LjMxOTUgMjcuMzAyN1YyNS4wODczSDI2Ljg5NzZWMjcuMzAyN0gyNi4zMTk1Wk0yNi42MDg1IDI0LjgyNTRDMjYuNDk4NiAyNC44MjU0IDI2LjQxNzMgMjQuNzk5OCAyNi4zNjQ2IDI0Ljc0ODZDMjYuMzEzNSAyNC42OTU5IDI2LjI4NzkgMjQuNjIzNiAyNi4yODc5IDI0LjUzMThWMjQuNTIwNUMyNi4yODc5IDI0LjQyODcgMjYuMzEzNSAyNC4zNTY0IDI2LjM2NDYgMjQuMzAzN0MyNi40MTczIDI0LjI1MSAyNi40OTg2IDI0LjIyNDcgMjYuNjA4NSAyNC4yMjQ3QzI2LjcxNjkgMjQuMjI0NyAyNi43OTc1IDI0LjI1MSAyNi44NTAyIDI0LjMwMzdDMjYuOTAyOSAyNC4zNTY0IDI2LjkyOTIgMjQuNDI4NyAyNi45MjkyIDI0LjUyMDVWMjQuNTMxOEMyNi45MjkyIDI0LjYyNTIgMjYuOTAyOSAyNC42OTc0IDI2Ljg1MDIgMjQuNzQ4NkMyNi43OTc1IDI0Ljc5OTggMjYuNzE2OSAyNC44MjU0IDI2LjYwODUgMjQuODI1NFpNMjguNDA5NCAyNC4yNDczQzI4LjUyNjggMjQuMjQ3MyAyOC42MzUyIDI0LjI1NyAyOC43MzQ1IDI0LjI3NjZDMjguODMzOSAyNC4yOTYyIDI4LjkyMiAyNC4zMjAzIDI4Ljk5ODggMjQuMzQ4OUwyOS4wNTA3IDI0Ljc4MjVDMjguOTg2IDI0Ljc2MjkgMjguOTE4MiAyNC43NDcxIDI4Ljg0NzUgMjQuNzM1MUMyOC43NzgyIDI0LjcyMTUgMjguNzAxNCAyNC43MTQ3IDI4LjYxNzEgMjQuNzE0N0MyOC41MTYyIDI0LjcxNDcgMjguNDM2NSAyNC43MjYgMjguMzc3NyAyNC43NDg2QzI4LjMyMDUgMjQuNzcxMiAyOC4yNzk5IDI0LjgwMzYgMjguMjU1OCAyNC44NDU3QzI4LjIzMTcgMjQuODg3OSAyOC4yMTk3IDI0LjkzNzUgMjguMjE5NyAyNC45OTQ4VjI1LjAwMTVDMjguMjE5NyAyNS4wNDIyIDI4LjIyNTcgMjUuMDgwNiAyOC4yMzc3IDI1LjExNjdDMjguMjQ5OCAyNS4xNTI4IDI4LjI2NDEgMjUuMTg1MiAyOC4yODA2IDI1LjIxMzhMMjcuOTAzNSAyNS4yMjc0VjI1LjE2NjRDMjcuODM0MiAyNS4xMzMzIDI3Ljc3NTUgMjUuMDg0MyAyNy43MjczIDI1LjAxOTZDMjcuNjc5MiAyNC45NTQ5IDI3LjY1NTEgMjQuODc0MyAyNy42NTUxIDI0Ljc3OFYyNC43NjY3QzI3LjY1NTEgMjQuNjA3MSAyNy43MTYxIDI0LjQ4MDYgMjcuODM4IDI0LjM4NzNDMjcuOTYxNSAyNC4yOTM5IDI4LjE1MTkgMjQuMjQ3MyAyOC40MDk0IDI0LjI0NzNaTTI3LjczMTkgMjcuMzAyN1YyNS4zNDcxSDI4LjMwNzdWMjcuMzAyN0gyNy43MzE5Wk0yNy40MTEyIDI1LjY0MDZWMjUuMTgyMkwyNy45NzEyIDI1LjE4NjdMMjguMTk3MSAyNS4xODIySDI5LjA0NjJMMjguOTk2NSAyNS42NDA2SDI3LjQxMTJaTTI5Ljg1OTIgMjQuMjQ3M0MyOS45NzY2IDI0LjI0NzMgMzAuMDg1IDI0LjI1NyAzMC4xODQ0IDI0LjI3NjZDMzAuMjgzNyAyNC4yOTYyIDMwLjM3MTggMjQuMzIwMyAzMC40NDg2IDI0LjM0ODlMMzAuNTAwNSAyNC43ODI1QzMwLjQzNTggMjQuNzYyOSAzMC4zNjggMjQuNzQ3MSAzMC4yOTczIDI0LjczNTFDMzAuMjI4IDI0LjcyMTUgMzAuMTUxMiAyNC43MTQ3IDMwLjA2NjkgMjQuNzE0N0MyOS45NjYxIDI0LjcxNDcgMjkuODg2MyAyNC43MjYgMjkuODI3NiAyNC43NDg2QzI5Ljc3MDMgMjQuNzcxMiAyOS43Mjk3IDI0LjgwMzYgMjkuNzA1NiAyNC44NDU3QzI5LjY4MTUgMjQuODg3OSAyOS42Njk1IDI0LjkzNzUgMjkuNjY5NSAyNC45OTQ4VjI1LjAwMTVDMjkuNjY5NSAyNS4wNDIyIDI5LjY3NTUgMjUuMDgwNiAyOS42ODc1IDI1LjExNjdDMjkuNjk5NiAyNS4xNTI4IDI5LjcxMzkgMjUuMTg1MiAyOS43MzA1IDI1LjIxMzhMMjkuMzUzMyAyNS4yMjc0VjI1LjE2NjRDMjkuMjg0MSAyNS4xMzMzIDI5LjIyNTMgMjUuMDg0MyAyOS4xNzcyIDI1LjAxOTZDMjkuMTI5IDI0Ljk1NDkgMjkuMTA0OSAyNC44NzQzIDI5LjEwNDkgMjQuNzc4VjI0Ljc2NjdDMjkuMTA0OSAyNC42MDcxIDI5LjE2NTkgMjQuNDgwNiAyOS4yODc4IDI0LjM4NzNDMjkuNDExMyAyNC4yOTM5IDI5LjYwMTcgMjQuMjQ3MyAyOS44NTkyIDI0LjI0NzNaTTI5LjE4MTcgMjcuMzAyN1YyNS4zNDcxSDI5Ljc1NzZWMjcuMzAyN0gyOS4xODE3Wk0yOC44NjEgMjUuNjQwNlYyNS4xODIyTDI5LjQyMTEgMjUuMTg2N0wyOS42NDY5IDI1LjE4MjJIMzAuNDk2TDMwLjQ0NjMgMjUuNjQwNkgyOC44NjFaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTUuOTg3NCAzMi4yODUyVjMxLjU1MDhIMTYuNzY5NEMxNi45OTM2IDMxLjU1MDggMTcuMTYxNyAzMS40OTAxIDE3LjI3MzkgMzEuMzY4N0MxNy4zODYgMzEuMjQ3MiAxNy40NDIgMzEuMDczNCAxNy40NDIgMzAuODQ3M1YzMC4xMjk4QzE3LjQ0MiAyOS45MDM3IDE3LjM4NiAyOS43MzA5IDE3LjI3MzkgMjkuNjExM0MxNy4xNjE3IDI5LjQ4OTkgMTYuOTkzNiAyOS40MjkxIDE2Ljc2OTQgMjkuNDI5MUgxNS45ODQ2VjI4LjcwMzJIMTYuODAwMkMxNy4zMTAzIDI4LjcwMzIgMTcuNjkxNSAyOC44MjY1IDE3Ljk0MzcgMjkuMDczMkMxOC4xOTc4IDI5LjMxOCAxOC4zMjQ5IDI5LjY3MDIgMTguMzI0OSAzMC4xMjk4VjMwLjg1MDFDMTguMzI0OSAzMS4zMTE3IDE4LjE5ODggMzEuNjY2NyAxNy45NDY1IDMxLjkxNTJDMTcuNjk0MyAzMi4xNjE4IDE3LjMxMjIgMzIuMjg1MiAxNi44MDAyIDMyLjI4NTJIMTUuOTg3NFpNMTUuMzczNiAzMi4yODUyVjI4LjcwMzJIMTYuMjM2OFYzMi4yODUySDE1LjM3MzZaTTE5LjA4NTIgMzIuMjg1MkwxOS4xOTczIDI4LjcwMzJIMjAuNDE5M0wyMS4wMTA3IDMwLjgzODlIMjEuMDQ3MkwyMS42Mzg1IDI4LjcwMzJIMjIuODYwNkwyMi45NzI3IDMyLjI4NTJIMjIuMTIzNEwyMi4wOTgyIDMxLjExMDhMMjIuMDczIDI5LjY4MTRIMjIuMDMwOUwyMS40MTcxIDMxLjg2NDdIMjAuNjM4TDIwLjAyNDIgMjkuNjgxNEgxOS45NzkzTDE5Ljk1NjkgMzEuMTEzNkwxOS45MzQ1IDMyLjI4NTJIMTkuMDg1MlpNMjQuNTIwOCAzMi4yODUyTDIzLjUzOTggMjguNzAzMkgyNC40NDc5TDI1LjE1MTQgMzEuNjMyMUgyNS4yMTU5TDI1LjkyMjIgMjguNzAzMkgyNi44Mjc1TDI1Ljg0OTMgMzIuMjg1MkgyNC41MjA4WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjAuOTc0MSAzLjU5OTYxTDIwLjU2NTggNC40MjQ4OUwxOS42NTQ1IDQuNTU4MTRMMjAuMzEyMiA1LjIwMjg5TDIwLjE1NzQgNi4xMTQxM0wyMC45NzQxIDUuNjg0M0wyMS43OTA4IDYuMTE0MTNMMjEuNjM2MSA1LjIwMjg5TDIyLjI5MzcgNC41NTgxNEwyMS4zODI1IDQuNDI0ODlMMjAuOTc0MSAzLjU5OTYxWiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjYuOTI3MyA0LjY3NDMyTDI2LjI2MSA1LjMxMDQ3TDI1LjM1ODQgNS4xMjEzNEwyNS43NTgxIDUuOTU1MjJMMjUuMzAyNSA2Ljc1NDcxTDI2LjIxMzcgNi42MzAwNkwyNi44MzcgNy4zMTM0OUwyNy4wMDAzIDYuNDA2NTRMMjcuODQyOCA2LjAyODI5TDI3LjAzMDQgNS41ODk4NkwyNi45MjczIDQuNjc0MzJaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zMi4xNTgzIDcuNzE3MjNMMzEuMzExNSA4LjA5MTE4TDMwLjUyOTIgNy42MDU0N0wzMC42MTk1IDguNTIxMDFMMjkuOTE0NiA5LjExODQ4TDMwLjgxNzIgOS4zMTYyTDMxLjE2NTQgMTAuMTY3M0wzMS42Mjk2IDkuMzcyMDhMMzIuNTQ5NCA5LjMwMzMxTDMxLjkzOTEgOC42MTU1OEwzMi4xNTgzIDcuNzE3MjNaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zNi4wMjY5IDEyLjM2ODVMMzUuMTA3MSAxMi40Mjg3TDM0LjUzNTQgMTEuNzA2NUwzNC4zMDc2IDEyLjYwMDZMMzMuNDQzNiAxMi45MTg3TDM0LjIyMTYgMTMuNDA4N0wzNC4yNjAzIDE0LjMzMjhMMzQuOTY5NSAxMy43NDRMMzUuODU1IDEzLjk5MzNMMzUuNTE1NCAxMy4xMzc5TDM2LjAyNjkgMTIuMzY4NVoiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTM4LjA3MjkgMTguMDYzNUwzNy4xOTE3IDE3LjgwNTZMMzYuODk5NCAxNi45Mjg3TDM2LjM3OTMgMTcuNjg5NUwzNS40NTk1IDE3LjY5MzhMMzYuMDIyNiAxOC40MjQ1TDM1Ljc0MzIgMTkuMzAxNEwzNi42MTE0IDE4Ljk5MTlMMzcuMzU5MyAxOS41MjkyTDM3LjMzMzUgMTguNjA5NEwzOC4wNzI5IDE4LjA2MzVaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zOC4wNTEzIDI0LjExMDdMMzcuMzA3NyAyMy41NjQ5TDM3LjMzNzggMjIuNjQ1TDM2LjU4NTUgMjMuMTgyM0wzNS43MTczIDIyLjg3MjhMMzYuMDAxIDIzLjc0OTdMMzUuNDMzNiAyNC40ODA0TDM2LjM1NzcgMjQuNDg0N0wzNi44Nzc4IDI1LjI0NTVMMzcuMTY1OCAyNC4zNjg2TDM4LjA1MTMgMjQuMTEwN1oiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTM1Ljk1ODEgMjkuNzg5NUwzNS40NDY1IDI5LjAyNDRMMzUuNzkwNCAyOC4xNjQ4TDM0LjkwMDcgMjguNDE4NEwzNC4xOTE0IDI3LjgyNTJMMzQuMTU3MSAyOC43NDkzTDMzLjM3NDggMjkuMjM5M0wzNC4yNDMgMjkuNTYxN0wzNC40NjY1IDMwLjQ1NThMMzUuMDM4MiAyOS43Mjk0TDM1Ljk1ODEgMjkuNzg5NVoiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTMyLjA1NTIgMzQuNDFMMzEuODM2IDMzLjUxNkwzMi40NDY0IDMyLjgyMzlMMzEuNTI2NiAzMi43NTUyTDMxLjA2MjMgMzEuOTZMMzAuNzE0MiAzMi44MTUzTDI5LjgxMTUgMzMuMDA4OEwzMC41MTY0IDMzLjYwNjJMMzAuNDI2MiAzNC41MjYxTDMxLjIwODUgMzQuMDQwNEwzMi4wNTUyIDM0LjQxWiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjYuODAyNyAzNy40MTQ2TDI2LjkwMTUgMzYuNDk5TDI3LjcxMzkgMzYuMDYwNkwyNi44NzU3IDM1LjY4MjNMMjYuNzA4MSAzNC43NzU0TDI2LjA4OTEgMzUuNDU4OEwyNS4xNzM2IDM1LjMzNDJMMjUuNjMzNSAzNi4xMzM3TDI1LjIzMzggMzYuOTYzMkwyNi4xMzY0IDM2Ljc3ODRMMjYuODAyNyAzNy40MTQ2WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMjAuODQwNyAzOC40NDE5TDIxLjI0OTEgMzcuNjE2NkwyMi4xNjAzIDM3LjQ4MzNMMjEuNDk4NCAzNi44Mzg2TDIxLjY1NzQgMzUuOTMxNkwyMC44NDA3IDM2LjM2MTVMMjAuMDI0IDM1LjkzMTZMMjAuMTc4OCAzNi44Mzg2TDE5LjUxNjggMzcuNDgzM0wyMC40MzI0IDM3LjYxNjZMMjAuODQwNyAzOC40NDE5WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMTQuODgzNCAzNy4zNjcyTDE1LjU0OTcgMzYuNzMxMUwxNi40NTIzIDM2LjkyMDJMMTYuMDUyNiAzNi4wODYzTDE2LjUxMjUgMzUuMjg2OEwxNS41OTY5IDM1LjQxMTVMMTQuOTc4IDM0LjcyOEwxNC44MTAzIDM1LjYzNUwxMy45NzIyIDM2LjAxNzVMMTQuNzg0NiAzNi40NTE3TDE0Ljg4MzQgMzcuMzY3MloiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTkuNjU2NjggMzQuMzI0MUwxMC40OTkxIDMzLjk1NDRMMTEuMjg1NyAzNC40MzU4TDExLjE5MTIgMzMuNTIwM0wxMS44OTYxIDMyLjkyMjhMMTAuOTkzNSAzMi43Mjk0TDEwLjY0NTMgMzEuODc0TDEwLjE4MTEgMzIuNjY5Mkw5LjI2MTIzIDMyLjczOEw5Ljg3NTg5IDMzLjQyNTdMOS42NTY2OCAzNC4zMjQxWiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNNS43ODM2OSAyOS42NzMzTDYuNzAzNTMgMjkuNjEzMUw3LjI3NTIxIDMwLjMzOTZMNy41MDMwMiAyOS40NDU1TDguMzY2OTkgMjkuMTIzMUw3LjU4ODk5IDI4LjYzMzFMNy41NTQ2IDI3LjcwOUw2Ljg0NTM4IDI4LjI5NzlMNS45NTU2MiAyOC4wNDg2TDYuMjk1MTkgMjguOTA4Mkw1Ljc4MzY5IDI5LjY3MzNaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zLjczNzc5IDIzLjk3NzlMNC42MjMyNSAyNC4yNDAxTDQuOTExMjQgMjUuMTEyN0w1LjQzMTMzIDI0LjM1MTlMNi4zNTU0NyAyNC4zNDc2TDUuNzg4MSAyMy42MTY5TDYuMDcxNzkgMjIuNzRMNS4yMDM1MiAyMy4wNDk1TDQuNDUxMzIgMjIuNTEyMkw0LjQ4MTQgMjMuNDM2M0wzLjczNzc5IDIzLjk3NzlaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik0zLjc1OTI4IDE3LjkzMDJMNC41MDI4OSAxOC40NzYxTDQuNDc3MSAxOS4zOTU5TDUuMjI1MDEgMTguODU4Nkw2LjA5MzI3IDE5LjE2ODFMNS44MTM4OCAxOC4yOTEyTDYuMzc2OTYgMTcuNTYwNUw1LjQ1MjgyIDE3LjU1NjJMNC45MzcwMiAxNi43OTU0TDQuNjQ0NzMgMTcuNjcyM0wzLjc1OTI4IDE3LjkzMDJaIiBmaWxsPSIjREM4NTQ4Ii8+CjxwYXRoIGQ9Ik01Ljg1MjU0IDEyLjI1MjJMNi4zNjQwNCAxMy4wMTczTDYuMDI0NDcgMTMuODc2OUw2LjkwOTkzIDEzLjYyNzZMNy42MTkxNSAxNC4yMTY1TDcuNjU3ODQgMTMuMjkyNEw4LjQzNTgzIDEyLjgwMjRMNy41NzE4NyAxMi40OEw3LjM0NDA2IDExLjU4NTlMNi43NzIzOCAxMi4zMTI0TDUuODUyNTQgMTIuMjUyMloiIGZpbGw9IiNEQzg1NDgiLz4KPHBhdGggZD0iTTkuNzU5NyA3LjYzMTc4TDkuOTc4OTIgOC41MzAxM0w5LjM2NDI2IDkuMjE3ODZMMTAuMjg0MSA5LjI4NjYzTDEwLjc0ODMgMTAuMDgxOEwxMS4xMDA4IDkuMjI2NDZMMTEuOTk5MSA5LjAzMzAzTDExLjI5NDIgOC40MzU1NkwxMS4zODg4IDcuNTIwMDJMMTAuNjAyMiA4LjAwMTQzTDkuNzU5NyA3LjYzMTc4WiIgZmlsbD0iI0RDODU0OCIvPgo8cGF0aCBkPSJNMTUuMDEyNSA0LjYyNjk1TDE0LjkwOTMgNS41NDI1TDE0LjA5NjkgNS45ODA5M0wxNC45Mzk0IDYuMzU5MThMMTUuMTAyNyA3LjI2NjEzTDE1LjcyNiA2LjU4MjY5TDE2LjYzNzIgNi43MDczNUwxNi4xODE2IDUuOTA3ODZMMTYuNTgxNCA1LjA3ODI4TDE1LjY3ODcgNS4yNjMxMUwxNS4wMTI1IDQuNjI2OTVaIiBmaWxsPSIjREM4NTQ4Ii8+CjxsaW5lIHgxPSI0OC45NzM4IiB5MT0iNC4zMjE3OCIgeDI9IjQ4Ljk3MzgiIHkyPSIzNy42Nzg0IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjAuNjExNjUzIi8+CjxwYXRoIGQ9Ik01Ny40MTEgMTguODkwNlYxNy40NjA2SDU5LjI5NkM1OS44ODk3IDE3LjQ2MDYgNjAuMzMzOCAxNy4zMDAzIDYwLjYyODUgMTYuOTc5NkM2MC45Mjc1IDE2LjY1NDYgNjEuMDc3IDE2LjE5MSA2MS4wNzcgMTUuNTg4NlYxMy44NjYxQzYxLjA3NyAxMy4yNjM4IDYwLjkyNzUgMTIuODAyMyA2MC42Mjg1IDEyLjQ4MTZDNjAuMzMzOCAxMi4xNjEgNTkuODg5NyAxMi4wMDA2IDU5LjI5NiAxMi4wMDA2SDU3LjQwNDVWMTAuNTgzNkg1OS4zNTQ1QzYwLjQ5ODUgMTAuNTgzNiA2MS4zNTg3IDEwLjg2NTMgNjEuOTM1IDExLjQyODZDNjIuNTExMyAxMS45OTIgNjIuNzk5NSAxMi44MDIzIDYyLjc5OTUgMTMuODU5NlYxNS41OTUxQzYyLjc5OTUgMTYuNjU2OCA2Mi41MTEzIDE3LjQ3MTUgNjEuOTM1IDE4LjAzOTFDNjEuMzYzIDE4LjYwNjggNjAuNTAyOCAxOC44OTA2IDU5LjM1NDUgMTguODkwNkg1Ny40MTFaTTU2LjIyMTUgMTguODkwNlYxMC41ODM2SDU3LjkwNVYxOC44OTA2SDU2LjIyMTVaTTY3LjA2NzggMTkuMDQ2NkM2NS45NzU4IDE5LjA0NjYgNjUuMTYzMyAxOC43OTUzIDY0LjYzMDMgMTguMjkyNkM2NC4wOTczIDE3Ljc5IDYzLjgzMDggMTcuMDYyIDYzLjgzMDggMTYuMTA4NlYxNS4yNzY2QzYzLjgzMDggMTQuMzMyIDY0LjA3OTkgMTMuNjA2MSA2NC41NzgzIDEzLjA5OTFDNjUuMDc2NiAxMi41OTIxIDY1LjgwMDMgMTIuMzM4NiA2Ni43NDkzIDEyLjMzODZDNjcuMzkwNiAxMi4zMzg2IDY3LjkyNTggMTIuNDUxMyA2OC4zNTQ4IDEyLjY3NjZDNjguNzgzOCAxMi45MDIgNjkuMTA0NCAxMy4yMjI2IDY5LjMxNjggMTMuNjM4NkM2OS41MzM0IDE0LjA1MDMgNjkuNjQxOCAxNC41NDQzIDY5LjY0MTggMTUuMTIwNlYxNS4zNDgxQzY5LjY0MTggMTUuNTA0MSA2OS42MzMxIDE1LjY2NDUgNjkuNjE1OCAxNS44MjkxQzY5LjYwMjggMTUuOTg5NSA2OS41ODMzIDE2LjE0MTEgNjkuNTU3MyAxNi4yODQxSDY4LjA0OTNDNjguMDYyMyAxNi4wNDU4IDY4LjA2ODggMTUuODIwNSA2OC4wNjg4IDE1LjYwODFDNjguMDczMSAxNS4zOTE1IDY4LjA3NTMgMTUuMTk2NSA2OC4wNzUzIDE1LjAyMzFDNjguMDc1MyAxNC43MjQxIDY4LjAyNzYgMTQuNDcwNiA2Ny45MzIzIDE0LjI2MjZDNjcuODM2OSAxNC4wNTAzIDY3LjY5MTggMTMuODkgNjcuNDk2OCAxMy43ODE2QzY3LjMwMTggMTMuNjczMyA2Ny4wNTI2IDEzLjYxOTEgNjYuNzQ5MyAxMy42MTkxQzY2LjMwMjkgMTMuNjE5MSA2NS45NzM2IDEzLjc0MjYgNjUuNzYxMyAxMy45ODk2QzY1LjU0ODkgMTQuMjM2NiA2NS40NDI4IDE0LjU4NzYgNjUuNDQyOCAxNS4wNDI2VjE1LjYzNDFMNjUuNDQ5MyAxNS44MjI2VjE2LjMyMzFDNjUuNDQ5MyAxNi41MjI1IDY1LjQ3OTYgMTYuNzA2NiA2NS41NDAzIDE2Ljg3NTZDNjUuNjA1MyAxNy4wNDQ2IDY1LjcxMTQgMTcuMTkyIDY1Ljg1ODggMTcuMzE3NkM2Ni4wMDYxIDE3LjQzOSA2Ni4yMDExIDE3LjUzNDMgNjYuNDQzOCAxNy42MDM2QzY2LjY5MDggMTcuNjczIDY2Ljk5ODQgMTcuNzA3NiA2Ny4zNjY4IDE3LjcwNzZDNjcuNzY1NCAxNy43MDc2IDY4LjE0NDYgMTcuNjY0MyA2OC41MDQzIDE3LjU3NzZDNjguODY4MyAxNy40ODY2IDY5LjIxMjggMTcuMzY1MyA2OS41Mzc4IDE3LjIxMzZMNjkuMzk0OCAxOC41MjY2QzY5LjEwNDQgMTguNjg3IDY4Ljc2MjEgMTguODEyNiA2OC4zNjc4IDE4LjkwMzZDNjcuOTc3OCAxOC45OTkgNjcuNTQ0NCAxOS4wNDY2IDY3LjA2NzggMTkuMDQ2NlpNNjQuNzE0OCAxNi4yODQxVjE1LjE3OTFINjkuMjE5M1YxNi4yODQxSDY0LjcxNDhaTTc0LjQ1MjUgMTkuMDQwMUM3NC4wNzExIDE5LjA0MDEgNzMuNzQ4MyAxOC45Nzk1IDczLjQ4NCAxOC44NTgxQzczLjIxOTYgMTguNzMyNSA3My4wMDUxIDE4LjU1NyA3Mi44NDA1IDE4LjMzMTZDNzIuNjgwMSAxOC4xMDYzIDcyLjU2NTMgMTcuODQyIDcyLjQ5NiAxNy41Mzg2SDcyLjAyMTVMNzIuNDQ0IDE2LjI2NDZDNzIuNDUyNiAxNi41NjggNzIuNTEzMyAxNi44MjM2IDcyLjYyNiAxNy4wMzE2QzcyLjc0MyAxNy4yMzk2IDcyLjkwMzMgMTcuMzk1NiA3My4xMDcgMTcuNDk5NkM3My4zMTUgMTcuNjAzNiA3My41NTc2IDE3LjY1NTYgNzMuODM1IDE3LjY1NTZDNzQuMjU1MyAxNy42NTU2IDc0LjU3NiAxNy41MzQzIDc0Ljc5NyAxNy4yOTE2Qzc1LjAxOCAxNy4wNDQ2IDc1LjEyODUgMTYuNjgwNiA3NS4xMjg1IDE2LjE5OTZWMTUuMTUzMUM3NS4xMjg1IDE0LjY3NjUgNzUuMDIwMSAxNC4zMTY4IDc0LjgwMzUgMTQuMDc0MUM3NC41ODY4IDEzLjgzMTUgNzQuMjY2MSAxMy43MTAxIDczLjg0MTUgMTMuNzEwMUM3My41OTg4IDEzLjcxMDEgNzMuMzggMTMuNzU3OCA3My4xODUgMTMuODUzMUM3Mi45OSAxMy45NDQxIDcyLjgyNzUgMTQuMDY3NiA3Mi42OTc1IDE0LjIyMzZDNzIuNTY3NSAxNC4zNzk2IDcyLjQ3NDMgMTQuNTU5NSA3Mi40MTggMTQuNzYzMUw3Mi4wMjggMTMuODY2MUg3Mi40ODk1QzcyLjU1ODggMTMuNTg4OCA3Mi42NjkzIDEzLjMzNzUgNzIuODIxIDEzLjExMjFDNzIuOTc3IDEyLjg4MjUgNzMuMTkxNSAxMi43MDI2IDczLjQ2NDUgMTIuNTcyNkM3My43NDE4IDEyLjQzODMgNzQuMDkwNiAxMi4zNzExIDc0LjUxMSAxMi4zNzExQzc1LjI2MDYgMTIuMzcxMSA3NS44MzA1IDEyLjYxMzggNzYuMjIwNSAxMy4wOTkxQzc2LjYxMDUgMTMuNTgwMSA3Ni44MDU1IDE0LjI5MyA3Ni44MDU1IDE1LjIzNzZWMTYuMTIxNkM3Ni44MDU1IDE3LjA3NSA3Ni42MDgzIDE3LjgwMDggNzYuMjE0IDE4LjI5OTFDNzUuODI0IDE4Ljc5MzEgNzUuMjM2OCAxOS4wNDAxIDc0LjQ1MjUgMTkuMDQwMVpNNzAuODEyNSAyMS4xMjY2VjEyLjUxNDFINzIuNDc2NUw3Mi40MTE1IDE0LjEzMjZMNzIuNDQ0IDE0LjQyNTFWMTYuOTc5Nkw3Mi40MjQ1IDE3LjI3ODZMNzIuNDcgMTkuMDI3MVYyMS4xMjY2SDcwLjgxMjVaTTgxLjcwMjcgMTguODkwNkw4MS43NjEyIDE3LjMzMDZMODEuNzE1NyAxNy4xODc2VjE1LjE5MjFMODEuNzA5MiAxNC45MDYxQzgxLjcwOTIgMTQuNDkwMSA4MS41OTQ0IDE0LjE4NDYgODEuMzY0NyAxMy45ODk2QzgxLjEzOTQgMTMuNzk0NiA4MC43Njg5IDEzLjY5NzEgODAuMjUzMiAxMy42OTcxQzc5LjgxNTUgMTMuNjk3MSA3OS40MDM5IDEzLjc1NTYgNzkuMDE4MiAxMy44NzI2Qzc4LjYzNjkgMTMuOTg1MyA3OC4yODM3IDE0LjExNzUgNzcuOTU4NyAxNC4yNjkxTDc4LjEwMTcgMTIuOTQzMUM3OC4yOTI0IDEyLjg0MzUgNzguNTA5IDEyLjc1MDMgNzguNzUxNyAxMi42NjM2Qzc4Ljk5ODcgMTIuNTcyNiA3OS4yNzM5IDEyLjQ5OSA3OS41NzcyIDEyLjQ0MjZDNzkuODgwNSAxMi4zODYzIDgwLjIwNzcgMTIuMzU4MSA4MC41NTg3IDEyLjM1ODFDODEuMDc4NyAxMi4zNTgxIDgxLjUxODUgMTIuNDIxIDgxLjg3ODIgMTIuNTQ2NkM4Mi4yMzc5IDEyLjY2OCA4Mi41MjM5IDEyLjg0MzUgODIuNzM2MiAxMy4wNzMxQzgyLjk1MjkgMTMuMzAyOCA4My4xMDg5IDEzLjU3OCA4My4yMDQyIDEzLjg5ODZDODMuMjk5NSAxNC4yMTUgODMuMzQ3MiAxNC41NjYgODMuMzQ3MiAxNC45NTE2VjE4Ljg5MDZIODEuNzAyN1pNNzkuNjE2MiAxOS4wNDAxQzc4Ljk4MzUgMTkuMDQwMSA3OC41MDA0IDE4Ljg4MiA3OC4xNjY3IDE4LjU2NTZDNzcuODM3NCAxOC4yNDkzIDc3LjY3MjcgMTcuNzk4NiA3Ny42NzI3IDE3LjIxMzZWMTcuMDMxNkM3Ny42NzI3IDE2LjQxMiA3Ny44NjM0IDE1Ljk1NDggNzguMjQ0NyAxNS42NjAxQzc4LjYyNiAxNS4zNjExIDc5LjIzMDUgMTUuMTU1MyA4MC4wNTgyIDE1LjA0MjZMODEuODY1MiAxNC43OTU2TDgxLjk2MjcgMTUuODY4MUw4MC4yOTg3IDE2LjEwODZDNzkuOTM0NyAxNi4xNTYzIDc5LjY3NDcgMTYuMjQzIDc5LjUxODcgMTYuMzY4NkM3OS4zNjcgMTYuNDk0MyA3OS4yOTEyIDE2LjY3ODUgNzkuMjkxMiAxNi45MjExVjE2Ljk4NjFDNzkuMjkxMiAxNy4yMjQ1IDc5LjM2NDkgMTcuNDEwOCA3OS41MTIyIDE3LjU0NTFDNzkuNjYzOSAxNy42NzUxIDc5LjkgMTcuNzQwMSA4MC4yMjA3IDE3Ljc0MDFDODAuNTA2NyAxNy43NDAxIDgwLjc1MTUgMTcuNjk0NiA4MC45NTUyIDE3LjYwMzZDODEuMTU4OSAxNy41MTI2IDgxLjMyNTcgMTcuMzkzNSA4MS40NTU3IDE3LjI0NjFDODEuNTkgMTcuMDk0NSA4MS42ODU0IDE2LjkyNTUgODEuNzQxNyAxNi43MzkxTDgxLjk3NTcgMTcuNTY0Nkg4MS42ODk3QzgxLjYyMDQgMTcuODM3NiA4MS41MDc3IDE4LjA4NjggODEuMzUxNyAxOC4zMTIxQzgxLjIgMTguNTMzMSA4MC45ODU1IDE4LjcxMDggODAuNzA4MiAxOC44NDUxQzgwLjQzMDkgMTguOTc1MSA4MC4wNjY5IDE5LjA0MDEgNzkuNjE2MiAxOS4wNDAxWk04Ni4zMzIzIDE1LjE4NTZMODUuOTA5OCAxNC4wNzQxSDg2LjMxMjhDODYuNDI5OCAxMy41NTg1IDg2LjY0MjIgMTMuMTUzMyA4Ni45NDk4IDEyLjg1ODZDODcuMjU3NSAxMi41NjQgODcuNjg0MyAxMi40MTY2IDg4LjIzMDMgMTIuNDE2NkM4OC4zNDMgMTIuNDE2NiA4OC40NDQ4IDEyLjQyNTMgODguNTM1OCAxMi40NDI2Qzg4LjYyNjggMTIuNDU1NiA4OC43MDkyIDEyLjQ3MyA4OC43ODI4IDEyLjQ5NDZMODguODczOCAxNC4xNTg2Qzg4Ljc3ODUgMTQuMTI4MyA4OC42NjggMTQuMTA2NiA4OC41NDIzIDE0LjA5MzZDODguNDE2NyAxNC4wNzYzIDg4LjI4NDUgMTQuMDY3NiA4OC4xNDU4IDE0LjA2NzZDODcuNzAzOCAxNC4wNjc2IDg3LjMyNjggMTQuMTY1MSA4Ny4wMTQ4IDE0LjM2MDFDODYuNzA3MiAxNC41NTUxIDg2LjQ3OTcgMTQuODMwMyA4Ni4zMzIzIDE1LjE4NTZaTTg0LjcxMzggMTguODkwNlYxMi41MTQxSDg2LjI5OThMODYuMjI4MyAxNC40NzA2TDg2LjM3NzggMTQuNTI5MVYxOC44OTA2SDg0LjcxMzhaTTkyLjQ3NzggMTkuMDI3MUM5MS45MzYxIDE5LjAyNzEgOTEuNTAyOCAxOC45NDcgOTEuMTc3OCAxOC43ODY2QzkwLjg1NzEgMTguNjIyIDkwLjYyNTMgMTguMzc1IDkwLjQ4MjMgMTguMDQ1NkM5MC4zMzkzIDE3LjcxNjMgOTAuMjY3OCAxNy4zMTExIDkwLjI2NzggMTYuODMwMVYxMy4xMTIxSDkxLjkxODhWMTYuNTgzMUM5MS45MTg4IDE2LjkyOTggOTEuOTk2OCAxNy4xODU1IDkyLjE1MjggMTcuMzUwMUM5Mi4zMTMxIDE3LjUxMDUgOTIuNTkyNiAxNy41OTA2IDkyLjk5MTMgMTcuNTkwNkM5My4yMjUzIDE3LjU5MDYgOTMuNDUwNiAxNy41NjY4IDkzLjY2NzMgMTcuNTE5MUM5My44ODQgMTcuNDY3MSA5NC4wODMzIDE3LjQgOTQuMjY1MyAxNy4zMTc2TDk0LjEyMjMgMTguNzA4NkM5My45MDU2IDE4LjgwODMgOTMuNjU2NSAxOC44ODYzIDkzLjM3NDggMTguOTQyNkM5My4wOTc1IDE4Ljk5OSA5Mi43OTg1IDE5LjAyNzEgOTIuNDc3OCAxOS4wMjcxWk04OS4zNDQ4IDEzLjg3OTFWMTIuNTY2MUg5NC4yMDY4TDk0LjA2MzggMTMuODc5MUg4OS4zNDQ4Wk05MC4yODczIDEyLjY4OTZMOTAuMjgwOCAxMC45OTk2TDkxLjkzODMgMTAuODMwNkw5MS44NzMzIDEyLjY4OTZIOTAuMjg3M1pNMTAzLjA4NCAxOC44OTA2VjE1LjAxNjZDMTAzLjA4NCAxNC43NTY2IDEwMy4wNTIgMTQuNTMzNSAxMDIuOTg3IDE0LjM0NzFDMTAyLjkyMiAxNC4xNTY1IDEwMi44MTMgMTQuMDA5MSAxMDIuNjYyIDEzLjkwNTFDMTAyLjUxNCAxMy44MDExIDEwMi4zMTMgMTMuNzQ5MSAxMDIuMDU3IDEzLjc0OTFDMTAxLjgyMyAxMy43NDkxIDEwMS42MTkgMTMuNzk0NiAxMDEuNDQ2IDEzLjg4NTZDMTAxLjI3MyAxMy45NzY2IDEwMS4xMzIgMTQuMTAwMSAxMDEuMDI0IDE0LjI1NjFDMTAwLjkxNSAxNC40MDc4IDEwMC44MzUgMTQuNTgxMSAxMDAuNzgzIDE0Ljc3NjFMMTAwLjYwMSAxMy44NjYxSDEwMC43N0MxMDAuODM5IDEzLjU5MzEgMTAwLjk1MiAxMy4zNDQgMTAxLjEwOCAxMy4xMTg2QzEwMS4yNjQgMTIuODg5IDEwMS40NzYgMTIuNzA3IDEwMS43NDUgMTIuNTcyNkMxMDIuMDE4IDEyLjQzODMgMTAyLjM2MyAxMi4zNzExIDEwMi43NzkgMTIuMzcxMUMxMDMuMjQyIDEyLjM3MTEgMTAzLjYxNyAxMi40NjIxIDEwMy45MDMgMTIuNjQ0MUMxMDQuMTkzIDEyLjgyMTggMTA0LjQwOCAxMy4wOTA1IDEwNC41NDcgMTMuNDUwMUMxMDQuNjg1IDEzLjgwNTUgMTA0Ljc1NSAxNC4yNDc1IDEwNC43NTUgMTQuNzc2MVYxOC44OTA2SDEwMy4wODRaTTk1LjIyNTYgMTguODkwNlYxMi41MTQxSDk2Ljg4OTZMOTYuODI0NiAxNC4xMzI2TDk2Ljg4OTYgMTQuMjA0MVYxOC44OTA2SDk1LjIyNTZaTTk5LjE1ODEgMTguODkwNlYxNS4wMTY2Qzk5LjE1ODEgMTQuNzU2NiA5OS4xMjU2IDE0LjUzMzUgOTkuMDYwNiAxNC4zNDcxQzk4Ljk5NTYgMTQuMTU2NSA5OC44ODcyIDE0LjAwOTEgOTguNzM1NiAxMy45MDUxQzk4LjU4ODIgMTMuODAxMSA5OC4zODY3IDEzLjc0OTEgOTguMTMxMSAxMy43NDkxQzk3Ljg5MjcgMTMuNzQ5MSA5Ny42ODY5IDEzLjc5NDYgOTcuNTEzNiAxMy44ODU2Qzk3LjM0NDYgMTMuOTc2NiA5Ny4yMDM3IDE0LjEwMDEgOTcuMDkxMSAxNC4yNTYxQzk2Ljk4MjcgMTQuNDA3OCA5Ni45MDI2IDE0LjU4MTEgOTYuODUwNiAxNC43NzYxTDk2LjU5MDYgMTMuODY2MUg5Ni45MDI2Qzk2Ljk2NzYgMTMuNTg0NSA5Ny4wNzU5IDEzLjMzMSA5Ny4yMjc2IDEzLjEwNTZDOTcuMzgzNiAxMi44ODAzIDk3LjU5MzcgMTIuNzAyNiA5Ny44NTgxIDEyLjU3MjZDOTguMTIyNCAxMi40MzgzIDk4LjQ1MTcgMTIuMzcxMSA5OC44NDYxIDEyLjM3MTFDOTkuNDM5NyAxMi4zNzExIDk5Ljg4NjEgMTIuNTIyOCAxMDAuMTg1IDEyLjgyNjFDMTAwLjQ4OCAxMy4xMjk1IDEwMC42NzkgMTMuNTcxNSAxMDAuNzU3IDE0LjE1MjFDMTAwLjc3NCAxNC4yMzg4IDEwMC43OSAxNC4zNDA2IDEwMC44MDMgMTQuNDU3NkMxMDAuODE2IDE0LjU3MDMgMTAwLjgyMiAxNC42NzY1IDEwMC44MjIgMTQuNzc2MVYxOC44OTA2SDk5LjE1ODFaTTEwOS4xNCAxOS4wNDY2QzEwOC4wNDggMTkuMDQ2NiAxMDcuMjM2IDE4Ljc5NTMgMTA2LjcwMyAxOC4yOTI2QzEwNi4xNyAxNy43OSAxMDUuOTAzIDE3LjA2MiAxMDUuOTAzIDE2LjEwODZWMTUuMjc2NkMxMDUuOTAzIDE0LjMzMiAxMDYuMTUyIDEzLjYwNjEgMTA2LjY1MSAxMy4wOTkxQzEwNy4xNDkgMTIuNTkyMSAxMDcuODczIDEyLjMzODYgMTA4LjgyMiAxMi4zMzg2QzEwOS40NjMgMTIuMzM4NiAxMDkuOTk4IDEyLjQ1MTMgMTEwLjQyNyAxMi42NzY2QzExMC44NTYgMTIuOTAyIDExMS4xNzcgMTMuMjIyNiAxMTEuMzg5IDEzLjYzODZDMTExLjYwNiAxNC4wNTAzIDExMS43MTQgMTQuNTQ0MyAxMTEuNzE0IDE1LjEyMDZWMTUuMzQ4MUMxMTEuNzE0IDE1LjUwNDEgMTExLjcwNSAxNS42NjQ1IDExMS42ODggMTUuODI5MUMxMTEuNjc1IDE1Ljk4OTUgMTExLjY1NiAxNi4xNDExIDExMS42MyAxNi4yODQxSDExMC4xMjJDMTEwLjEzNSAxNi4wNDU4IDExMC4xNDEgMTUuODIwNSAxMTAuMTQxIDE1LjYwODFDMTEwLjE0NSAxNS4zOTE1IDExMC4xNDggMTUuMTk2NSAxMTAuMTQ4IDE1LjAyMzFDMTEwLjE0OCAxNC43MjQxIDExMC4xIDE0LjQ3MDYgMTEwLjAwNSAxNC4yNjI2QzEwOS45MDkgMTQuMDUwMyAxMDkuNzY0IDEzLjg5IDEwOS41NjkgMTMuNzgxNkMxMDkuMzc0IDEzLjY3MzMgMTA5LjEyNSAxMy42MTkxIDEwOC44MjIgMTMuNjE5MUMxMDguMzc1IDEzLjYxOTEgMTA4LjA0NiAxMy43NDI2IDEwNy44MzQgMTMuOTg5NkMxMDcuNjIxIDE0LjIzNjYgMTA3LjUxNSAxNC41ODc2IDEwNy41MTUgMTUuMDQyNlYxNS42MzQxTDEwNy41MjIgMTUuODIyNlYxNi4zMjMxQzEwNy41MjIgMTYuNTIyNSAxMDcuNTUyIDE2LjcwNjYgMTA3LjYxMyAxNi44NzU2QzEwNy42NzggMTcuMDQ0NiAxMDcuNzg0IDE3LjE5MiAxMDcuOTMxIDE3LjMxNzZDMTA4LjA3OCAxNy40MzkgMTA4LjI3MyAxNy41MzQzIDEwOC41MTYgMTcuNjAzNkMxMDguNzYzIDE3LjY3MyAxMDkuMDcxIDE3LjcwNzYgMTA5LjQzOSAxNy43MDc2QzEwOS44MzggMTcuNzA3NiAxMTAuMjE3IDE3LjY2NDMgMTEwLjU3NyAxNy41Nzc2QzExMC45NDEgMTcuNDg2NiAxMTEuMjg1IDE3LjM2NTMgMTExLjYxIDE3LjIxMzZMMTExLjQ2NyAxOC41MjY2QzExMS4xNzcgMTguNjg3IDExMC44MzQgMTguODEyNiAxMTAuNDQgMTguOTAzNkMxMTAuMDUgMTguOTk5IDEwOS42MTcgMTkuMDQ2NiAxMDkuMTQgMTkuMDQ2NlpNMTA2Ljc4NyAxNi4yODQxVjE1LjE3OTFIMTExLjI5MlYxNi4yODQxSDEwNi43ODdaTTExNy4wMzggMTguODkwNlYxNS4wNjIxQzExNy4wMzggMTQuNzkzNSAxMTcuMDAxIDE0LjU2MTYgMTE2LjkyOCAxNC4zNjY2QzExNi44NTggMTQuMTcxNiAxMTYuNzQxIDE0LjAyIDExNi41NzcgMTMuOTExNkMxMTYuNDEyIDEzLjgwMzMgMTE2LjE4NyAxMy43NDkxIDExNS45MDEgMTMuNzQ5MUMxMTUuNjQ5IDEzLjc0OTEgMTE1LjQyOCAxMy43OTQ2IDExNS4yMzggMTMuODg1NkMxMTUuMDUxIDEzLjk3NjYgMTE0Ljg5OCAxNC4xMDAxIDExNC43NzYgMTQuMjU2MUMxMTQuNjU5IDE0LjQwNzggMTE0LjU3IDE0LjU4MTEgMTE0LjUxIDE0Ljc3NjFMMTE0LjI1IDEzLjg2NjFIMTE0LjU2MkMxMTQuNjMxIDEzLjU4NDUgMTE0Ljc0NiAxMy4zMzEgMTE0LjkwNiAxMy4xMDU2QzExNS4wNzEgMTIuODgwMyAxMTUuMjkyIDEyLjcwMjYgMTE1LjU2OSAxMi41NzI2QzExNS44NTEgMTIuNDM4MyAxMTYuMjAyIDEyLjM3MTEgMTE2LjYyMiAxMi4zNzExQzExNy4xMTIgMTIuMzcxMSAxMTcuNTA4IDEyLjQ2NDMgMTE3LjgxMiAxMi42NTA2QzExOC4xMTUgMTIuODMyNiAxMTguMzM4IDEzLjEwNTYgMTE4LjQ4MSAxMy40Njk2QzExOC42MjkgMTMuODMzNiAxMTguNzAyIDE0LjI4NDMgMTE4LjcwMiAxNC44MjE2VjE4Ljg5MDZIMTE3LjAzOFpNMTEyLjg4NSAxOC44OTA2VjEyLjUxNDFIMTE0LjU0OUwxMTQuNDg0IDE0LjA2NzZMMTE0LjU0OSAxNC4yMDQxVjE4Ljg5MDZIMTEyLjg4NVpNMTIyLjU0IDE5LjAyNzFDMTIxLjk5OSAxOS4wMjcxIDEyMS41NjUgMTguOTQ3IDEyMS4yNCAxOC43ODY2QzEyMC45MiAxOC42MjIgMTIwLjY4OCAxOC4zNzUgMTIwLjU0NSAxOC4wNDU2QzEyMC40MDIgMTcuNzE2MyAxMjAuMzMgMTcuMzExMSAxMjAuMzMgMTYuODMwMVYxMy4xMTIxSDEyMS45ODFWMTYuNTgzMUMxMjEuOTgxIDE2LjkyOTggMTIyLjA1OSAxNy4xODU1IDEyMi4yMTUgMTcuMzUwMUMxMjIuMzc2IDE3LjUxMDUgMTIyLjY1NSAxNy41OTA2IDEyMy4wNTQgMTcuNTkwNkMxMjMuMjg4IDE3LjU5MDYgMTIzLjUxMyAxNy41NjY4IDEyMy43MyAxNy41MTkxQzEyMy45NDYgMTcuNDY3MSAxMjQuMTQ2IDE3LjQgMTI0LjMyOCAxNy4zMTc2TDEyNC4xODUgMTguNzA4NkMxMjMuOTY4IDE4LjgwODMgMTIzLjcxOSAxOC44ODYzIDEyMy40MzcgMTguOTQyNkMxMjMuMTYgMTguOTk5IDEyMi44NjEgMTkuMDI3MSAxMjIuNTQgMTkuMDI3MVpNMTE5LjQwNyAxMy44NzkxVjEyLjU2NjFIMTI0LjI2OUwxMjQuMTI2IDEzLjg3OTFIMTE5LjQwN1pNMTIwLjM1IDEyLjY4OTZMMTIwLjM0MyAxMC45OTk2TDEyMi4wMDEgMTAuODMwNkwxMjEuOTM2IDEyLjY4OTZIMTIwLjM1Wk0xMzAuNzIzIDE5LjA2NjFDMTI5LjczNSAxOS4wNjYxIDEyOC45ODUgMTguODEyNiAxMjguNDc0IDE4LjMwNTZDMTI3Ljk2NyAxNy43OTg2IDEyNy43MTQgMTcuMDc3MSAxMjcuNzE0IDE2LjE0MTFWMTUuMjc2NkMxMjcuNzE0IDE0LjMzNjMgMTI3Ljk2NyAxMy42MTI2IDEyOC40NzQgMTMuMTA1NkMxMjguOTg1IDEyLjU5NDMgMTI5LjczNSAxMi4zMzg2IDEzMC43MjMgMTIuMzM4NkMxMzEuNzA3IDEyLjMzODYgMTMyLjQ1MiAxMi41OTQzIDEzMi45NTkgMTMuMTA1NkMxMzMuNDY2IDEzLjYxMjYgMTMzLjcyIDE0LjMzNjMgMTMzLjcyIDE1LjI3NjZWMTYuMTQxMUMxMzMuNzIgMTcuMDc3MSAxMzMuNDY2IDE3Ljc5ODYgMTMyLjk1OSAxOC4zMDU2QzEzMi40NTYgMTguODEyNiAxMzEuNzExIDE5LjA2NjEgMTMwLjcyMyAxOS4wNjYxWk0xMzAuNzIzIDE3LjczMzZDMTMxLjE1NiAxNy43MzM2IDEzMS40ODYgMTcuNjAzNiAxMzEuNzExIDE3LjM0MzZDMTMxLjk0MSAxNy4wODM2IDEzMi4wNTYgMTYuNzExIDEzMi4wNTYgMTYuMjI1NlYxNS4xOTIxQzEzMi4wNTYgMTQuNjk4MSAxMzEuOTQxIDE0LjMyMTEgMTMxLjcxMSAxNC4wNjExQzEzMS40ODYgMTMuNzk2OCAxMzEuMTU2IDEzLjY2NDYgMTMwLjcyMyAxMy42NjQ2QzEzMC4yODUgMTMuNjY0NiAxMjkuOTUyIDEzLjc5NjggMTI5LjcyMiAxNC4wNjExQzEyOS40OTcgMTQuMzIxMSAxMjkuMzg0IDE0LjY5ODEgMTI5LjM4NCAxNS4xOTIxVjE2LjIyNTZDMTI5LjM4NCAxNi43MTEgMTI5LjQ5NyAxNy4wODM2IDEyOS43MjIgMTcuMzQzNkMxMjkuOTUyIDE3LjYwMzYgMTMwLjI4NSAxNy43MzM2IDEzMC43MjMgMTcuNzMzNlpNMTM3LjE1NCAxMC4wOTYxQzEzNy40OTIgMTAuMDk2MSAxMzcuODA0IDEwLjEyNDMgMTM4LjA5IDEwLjE4MDZDMTM4LjM3NiAxMC4yMzcgMTM4LjYyOSAxMC4zMDYzIDEzOC44NSAxMC4zODg2TDEzOSAxMS42MzY2QzEzOC44MTMgMTEuNTgwMyAxMzguNjE4IDExLjUzNDggMTM4LjQxNSAxMS41MDAxQzEzOC4yMTUgMTEuNDYxMSAxMzcuOTk0IDExLjQ0MTYgMTM3Ljc1MiAxMS40NDE2QzEzNy40NjEgMTEuNDQxNiAxMzcuMjMyIDExLjQ3NDEgMTM3LjA2MyAxMS41MzkxQzEzNi44OTggMTEuNjA0MSAxMzYuNzgxIDExLjY5NzMgMTM2LjcxMiAxMS44MTg2QzEzNi42NDIgMTEuOTQgMTM2LjYwOCAxMi4wODMgMTM2LjYwOCAxMi4yNDc2VjEyLjI2NzFDMTM2LjYwOCAxMi4zODQxIDEzNi42MjUgMTIuNDk0NiAxMzYuNjYgMTIuNTk4NkMxMzYuNjk0IDEyLjcwMjYgMTM2LjczNSAxMi43OTU4IDEzNi43ODMgMTIuODc4MUwxMzUuNjk4IDEyLjkxNzFWMTIuNzQxNkMxMzUuNDk4IDEyLjY0NjMgMTM1LjMyOSAxMi41MDU1IDEzNS4xOTEgMTIuMzE5MUMxMzUuMDUyIDEyLjEzMjggMTM0Ljk4MyAxMS45MDEgMTM0Ljk4MyAxMS42MjM2VjExLjU5MTFDMTM0Ljk4MyAxMS4xMzE4IDEzNS4xNTggMTAuNzY3OCAxMzUuNTA5IDEwLjQ5OTFDMTM1Ljg2NCAxMC4yMzA1IDEzNi40MTMgMTAuMDk2MSAxMzcuMTU0IDEwLjA5NjFaTTEzNS4yMDQgMTguODkwNlYxMy4yNjE2SDEzNi44NjFWMTguODkwNkgxMzUuMjA0Wk0xMzQuMjgxIDE0LjEwNjZWMTIuNzg3MUwxMzUuODkzIDEyLjgwMDFMMTM2LjU0MyAxMi43ODcxSDEzOC45ODdMMTM4Ljg0NCAxNC4xMDY2SDEzNC4yODFaTTU2LjExNzUgMzEuODkwNkw1Ni4zOTA1IDIzLjU4MzZINTguODQ3NUw2MC40MDc1IDI4Ljg2ODFINjAuNDkyTDYyLjA0NTUgMjMuNTgzNkg2NC40OTZMNjQuNzc1NSAzMS44OTA2SDYzLjEyNDVMNjMuMDQ2NSAyOC45MDA2TDYyLjk3NSAyNS40NDkxSDYyLjg3MUw2MS4yMzk1IDMwLjkyMjFINTkuNjUzNUw1OC4wMTU1IDI1LjQ0OTFINTcuOTExNUw1Ny44NCAyOC45MDcxTDU3Ljc2ODUgMzEuODkwNkg1Ni4xMTc1Wk02OC45NjA0IDMyLjA2NjFDNjcuOTcyNCAzMi4wNjYxIDY3LjIyMjcgMzEuODEyNiA2Ni43MTE0IDMxLjMwNTZDNjYuMjA0NCAzMC43OTg2IDY1Ljk1MDkgMzAuMDc3MSA2NS45NTA5IDI5LjE0MTFWMjguMjc2NkM2NS45NTA5IDI3LjMzNjMgNjYuMjA0NCAyNi42MTI2IDY2LjcxMTQgMjYuMTA1NkM2Ny4yMjI3IDI1LjU5NDMgNjcuOTcyNCAyNS4zMzg2IDY4Ljk2MDQgMjUuMzM4NkM2OS45NDQgMjUuMzM4NiA3MC42ODk0IDI1LjU5NDMgNzEuMTk2NCAyNi4xMDU2QzcxLjcwMzQgMjYuNjEyNiA3MS45NTY5IDI3LjMzNjMgNzEuOTU2OSAyOC4yNzY2VjI5LjE0MTFDNzEuOTU2OSAzMC4wNzcxIDcxLjcwMzQgMzAuNzk4NiA3MS4xOTY0IDMxLjMwNTZDNzAuNjkzNyAzMS44MTI2IDY5Ljk0ODQgMzIuMDY2MSA2OC45NjA0IDMyLjA2NjFaTTY4Ljk2MDQgMzAuNzMzNkM2OS4zOTM3IDMwLjczMzYgNjkuNzIzIDMwLjYwMzYgNjkuOTQ4NCAzMC4zNDM2QzcwLjE3OCAzMC4wODM2IDcwLjI5MjkgMjkuNzExIDcwLjI5MjkgMjkuMjI1NlYyOC4xOTIxQzcwLjI5MjkgMjcuNjk4MSA3MC4xNzggMjcuMzIxMSA2OS45NDg0IDI3LjA2MTFDNjkuNzIzIDI2Ljc5NjggNjkuMzkzNyAyNi42NjQ2IDY4Ljk2MDQgMjYuNjY0NkM2OC41MjI3IDI2LjY2NDYgNjguMTg5IDI2Ljc5NjggNjcuOTU5NCAyNy4wNjExQzY3LjczNCAyNy4zMjExIDY3LjYyMTQgMjcuNjk4MSA2Ny42MjE0IDI4LjE5MjFWMjkuMjI1NkM2Ny42MjE0IDI5LjcxMSA2Ny43MzQgMzAuMDgzNiA2Ny45NTk0IDMwLjM0MzZDNjguMTg5IDMwLjYwMzYgNjguNTIyNyAzMC43MzM2IDY4Ljk2MDQgMzAuNzMzNlpNNzUuNjA1NyAzMi4wMjcxQzc1LjA2NCAzMi4wMjcxIDc0LjYzMDcgMzEuOTQ3IDc0LjMwNTcgMzEuNzg2NkM3My45ODUgMzEuNjIyIDczLjc1MzIgMzEuMzc1IDczLjYxMDIgMzEuMDQ1NkM3My40NjcyIDMwLjcxNjMgNzMuMzk1NyAzMC4zMTExIDczLjM5NTcgMjkuODMwMVYyNi4xMTIxSDc1LjA0NjdWMjkuNTgzMUM3NS4wNDY3IDI5LjkyOTggNzUuMTI0NyAzMC4xODU1IDc1LjI4MDcgMzAuMzUwMUM3NS40NDEgMzAuNTEwNSA3NS43MjA1IDMwLjU5MDYgNzYuMTE5MiAzMC41OTA2Qzc2LjM1MzIgMzAuNTkwNiA3Ni41Nzg1IDMwLjU2NjggNzYuNzk1MiAzMC41MTkxQzc3LjAxMTkgMzAuNDY3MSA3Ny4yMTEyIDMwLjQgNzcuMzkzMiAzMC4zMTc2TDc3LjI1MDIgMzEuNzA4NkM3Ny4wMzM1IDMxLjgwODMgNzYuNzg0NCAzMS44ODYzIDc2LjUwMjcgMzEuOTQyNkM3Ni4yMjU0IDMxLjk5OSA3NS45MjY0IDMyLjAyNzEgNzUuNjA1NyAzMi4wMjcxWk03Mi40NzI3IDI2Ljg3OTFWMjUuNTY2MUg3Ny4zMzQ3TDc3LjE5MTcgMjYuODc5MUg3Mi40NzI3Wk03My40MTUyIDI1LjY4OTZMNzMuNDA4NyAyMy45OTk2TDc1LjA2NjIgMjMuODMwNkw3NS4wMDEyIDI1LjY4OTZINzMuNDE1MlpNODEuMDA4MiAzMi4wNjYxQzgwLjAyMDIgMzIuMDY2MSA3OS4yNzA2IDMxLjgxMjYgNzguNzU5MiAzMS4zMDU2Qzc4LjI1MjIgMzAuNzk4NiA3Ny45OTg3IDMwLjA3NzEgNzcuOTk4NyAyOS4xNDExVjI4LjI3NjZDNzcuOTk4NyAyNy4zMzYzIDc4LjI1MjIgMjYuNjEyNiA3OC43NTkyIDI2LjEwNTZDNzkuMjcwNiAyNS41OTQzIDgwLjAyMDIgMjUuMzM4NiA4MS4wMDgyIDI1LjMzODZDODEuOTkxOSAyNS4zMzg2IDgyLjczNzIgMjUuNTk0MyA4My4yNDQyIDI2LjEwNTZDODMuNzUxMiAyNi42MTI2IDg0LjAwNDcgMjcuMzM2MyA4NC4wMDQ3IDI4LjI3NjZWMjkuMTQxMUM4NC4wMDQ3IDMwLjA3NzEgODMuNzUxMiAzMC43OTg2IDgzLjI0NDIgMzEuMzA1NkM4Mi43NDE2IDMxLjgxMjYgODEuOTk2MiAzMi4wNjYxIDgxLjAwODIgMzIuMDY2MVpNODEuMDA4MiAzMC43MzM2QzgxLjQ0MTYgMzAuNzMzNiA4MS43NzA5IDMwLjYwMzYgODEuOTk2MiAzMC4zNDM2QzgyLjIyNTkgMzAuMDgzNiA4Mi4zNDA3IDI5LjcxMSA4Mi4zNDA3IDI5LjIyNTZWMjguMTkyMUM4Mi4zNDA3IDI3LjY5ODEgODIuMjI1OSAyNy4zMjExIDgxLjk5NjIgMjcuMDYxMUM4MS43NzA5IDI2Ljc5NjggODEuNDQxNiAyNi42NjQ2IDgxLjAwODIgMjYuNjY0NkM4MC41NzA2IDI2LjY2NDYgODAuMjM2OSAyNi43OTY4IDgwLjAwNzIgMjcuMDYxMUM3OS43ODE5IDI3LjMyMTEgNzkuNjY5MiAyNy42OTgxIDc5LjY2OTIgMjguMTkyMVYyOS4yMjU2Qzc5LjY2OTIgMjkuNzExIDc5Ljc4MTkgMzAuMDgzNiA4MC4wMDcyIDMwLjM0MzZDODAuMjM2OSAzMC42MDM2IDgwLjU3MDYgMzAuNzMzNiA4MS4wMDgyIDMwLjczMzZaTTg2Ljg0MDIgMjguMTg1Nkw4Ni40MTc3IDI3LjA3NDFIODYuODIwN0M4Ni45Mzc3IDI2LjU1ODUgODcuMTUgMjYuMTUzMyA4Ny40NTc3IDI1Ljg1ODZDODcuNzY1MyAyNS41NjQgODguMTkyMiAyNS40MTY2IDg4LjczODIgMjUuNDE2NkM4OC44NTA4IDI1LjQxNjYgODguOTUyNyAyNS40MjUzIDg5LjA0MzcgMjUuNDQyNkM4OS4xMzQ3IDI1LjQ1NTYgODkuMjE3IDI1LjQ3MyA4OS4yOTA3IDI1LjQ5NDZMODkuMzgxNyAyNy4xNTg2Qzg5LjI4NjMgMjcuMTI4MyA4OS4xNzU4IDI3LjEwNjYgODkuMDUwMiAyNy4wOTM2Qzg4LjkyNDUgMjcuMDc2MyA4OC43OTIzIDI3LjA2NzYgODguNjUzNyAyNy4wNjc2Qzg4LjIxMTcgMjcuMDY3NiA4Ny44MzQ3IDI3LjE2NTEgODcuNTIyNyAyNy4zNjAxQzg3LjIxNSAyNy41NTUxIDg2Ljk4NzUgMjcuODMwMyA4Ni44NDAyIDI4LjE4NTZaTTg1LjIyMTcgMzEuODkwNlYyNS41MTQxSDg2LjgwNzdMODYuNzM2MiAyNy40NzA2TDg2Ljg4NTcgMjcuNTI5MVYzMS44OTA2SDg1LjIyMTdaTTk0Ljc2NzIgMzEuODkwNkw5Mi40MDc3IDIzLjU4MzZIOTQuMTc1N0w5NS45OTU3IDMwLjYxNjZIOTYuMTQ1Mkw5Ny45NjUyIDIzLjU4MzZIOTkuNzMzMkw5Ny4zODAyIDMxLjg5MDZIOTQuNzY3MlpNMTAzLjA0NiAzMi4wNDY2QzEwMS45NTQgMzIuMDQ2NiAxMDEuMTQyIDMxLjc5NTMgMTAwLjYwOSAzMS4yOTI2QzEwMC4wNzYgMzAuNzkgOTkuODA5MyAzMC4wNjIgOTkuODA5MyAyOS4xMDg2VjI4LjI3NjZDOTkuODA5MyAyNy4zMzIgMTAwLjA1OCAyNi42MDYxIDEwMC41NTcgMjYuMDk5MUMxMDEuMDU1IDI1LjU5MjEgMTAxLjc3OSAyNS4zMzg2IDEwMi43MjggMjUuMzM4NkMxMDMuMzY5IDI1LjMzODYgMTAzLjkwNCAyNS40NTEzIDEwNC4zMzMgMjUuNjc2NkMxMDQuNzYyIDI1LjkwMiAxMDUuMDgzIDI2LjIyMjYgMTA1LjI5NSAyNi42Mzg2QzEwNS41MTIgMjcuMDUwMyAxMDUuNjIgMjcuNTQ0MyAxMDUuNjIgMjguMTIwNlYyOC4zNDgxQzEwNS42MiAyOC41MDQxIDEwNS42MTIgMjguNjY0NSAxMDUuNTk0IDI4LjgyOTFDMTA1LjU4MSAyOC45ODk1IDEwNS41NjIgMjkuMTQxMSAxMDUuNTM2IDI5LjI4NDFIMTA0LjAyOEMxMDQuMDQxIDI5LjA0NTggMTA0LjA0NyAyOC44MjA1IDEwNC4wNDcgMjguNjA4MUMxMDQuMDUyIDI4LjM5MTUgMTA0LjA1NCAyOC4xOTY1IDEwNC4wNTQgMjguMDIzMUMxMDQuMDU0IDI3LjcyNDEgMTA0LjAwNiAyNy40NzA2IDEwMy45MTEgMjcuMjYyNkMxMDMuODE1IDI3LjA1MDMgMTAzLjY3IDI2Ljg5IDEwMy40NzUgMjYuNzgxNkMxMDMuMjggMjYuNjczMyAxMDMuMDMxIDI2LjYxOTEgMTAyLjcyOCAyNi42MTkxQzEwMi4yODEgMjYuNjE5MSAxMDEuOTUyIDI2Ljc0MjYgMTAxLjc0IDI2Ljk4OTZDMTAxLjUyNyAyNy4yMzY2IDEwMS40MjEgMjcuNTg3NiAxMDEuNDIxIDI4LjA0MjZWMjguNjM0MUwxMDEuNDI4IDI4LjgyMjZWMjkuMzIzMUMxMDEuNDI4IDI5LjUyMjUgMTAxLjQ1OCAyOS43MDY2IDEwMS41MTkgMjkuODc1NkMxMDEuNTg0IDMwLjA0NDYgMTAxLjY5IDMwLjE5MiAxMDEuODM3IDMwLjMxNzZDMTAxLjk4NSAzMC40MzkgMTAyLjE4IDMwLjUzNDMgMTAyLjQyMiAzMC42MDM2QzEwMi42NjkgMzAuNjczIDEwMi45NzcgMzAuNzA3NiAxMDMuMzQ1IDMwLjcwNzZDMTAzLjc0NCAzMC43MDc2IDEwNC4xMjMgMzAuNjY0MyAxMDQuNDgzIDMwLjU3NzZDMTA0Ljg0NyAzMC40ODY2IDEwNS4xOTEgMzAuMzY1MyAxMDUuNTE2IDMwLjIxMzZMMTA1LjM3MyAzMS41MjY2QzEwNS4wODMgMzEuNjg3IDEwNC43NDEgMzEuODEyNiAxMDQuMzQ2IDMxLjkwMzZDMTAzLjk1NiAzMS45OTkgMTAzLjUyMyAzMi4wNDY2IDEwMy4wNDYgMzIuMDQ2NlpNMTAwLjY5MyAyOS4yODQxVjI4LjE3OTFIMTA1LjE5OFYyOS4yODQxSDEwMC42OTNaTTExMC45NDQgMzEuODkwNlYyOC4wNjIxQzExMC45NDQgMjcuNzkzNSAxMTAuOTA4IDI3LjU2MTYgMTEwLjgzNCAyNy4zNjY2QzExMC43NjUgMjcuMTcxNiAxMTAuNjQ4IDI3LjAyIDExMC40ODMgMjYuOTExNkMxMTAuMzE4IDI2LjgwMzMgMTEwLjA5MyAyNi43NDkxIDEwOS44MDcgMjYuNzQ5MUMxMDkuNTU2IDI2Ljc0OTEgMTA5LjMzNyAyNi43OTQ2IDEwOS4xNSAyNi44ODU2QzEwOC45NjQgMjYuOTc2NiAxMDguODEgMjcuMTAwMSAxMDguNjg5IDI3LjI1NjFDMTA4LjU3MiAyNy40MDc4IDEwOC40ODUgMjcuNTgxMSAxMDguNDI5IDI3Ljc3NjFMMTA4LjA5MSAyNi44NjYxSDEwOC40ODdDMTA4LjU2MSAyNi41ODQ1IDEwOC42NzggMjYuMzMxIDEwOC44MzggMjYuMTA1NkMxMDguOTk5IDI1Ljg4MDMgMTA5LjIxOCAyNS43MDI2IDEwOS40OTUgMjUuNTcyNkMxMDkuNzcyIDI1LjQzODMgMTEwLjExNyAyNS4zNzExIDExMC41MjggMjUuMzcxMUMxMTEuMDE4IDI1LjM3MTEgMTExLjQxNSAyNS40NjQzIDExMS43MTggMjUuNjUwNkMxMTIuMDIxIDI1LjgzMjYgMTEyLjI0NCAyNi4xMDU2IDExMi4zODcgMjYuNDY5NkMxMTIuNTM1IDI2LjgzMzYgMTEyLjYwOCAyNy4yODQzIDExMi42MDggMjcuODIxNlYzMS44OTA2SDExMC45NDRaTTEwNi43OTEgMzEuODkwNlYyMy4yOTc2SDEwOC40NDhWMjUuMjYwNkwxMDguNDE2IDI3LjI0MzFMMTA4LjQ1NSAyNy4zNjY2VjMxLjg5MDZIMTA2Ljc5MVpNMTE0LjAxNSAzMS44OTA2VjI1LjUxNDFIMTE1LjY3OVYzMS44OTA2SDExNC4wMTVaTTExNC44NDcgMjQuNzYwMUMxMTQuNTMxIDI0Ljc2MDEgMTE0LjI5NyAyNC42ODY1IDExNC4xNDUgMjQuNTM5MUMxMTMuOTk4IDI0LjM4NzUgMTEzLjkyNCAyNC4xNzk1IDExMy45MjQgMjMuOTE1MVYyMy44ODI2QzExMy45MjQgMjMuNjE4MyAxMTMuOTk4IDIzLjQxMDMgMTE0LjE0NSAyMy4yNTg2QzExNC4yOTcgMjMuMTA3IDExNC41MzEgMjMuMDMxMSAxMTQuODQ3IDIzLjAzMTFDMTE1LjE1OSAyMy4wMzExIDExNS4zOTEgMjMuMTA3IDExNS41NDIgMjMuMjU4NkMxMTUuNjk0IDIzLjQxMDMgMTE1Ljc3IDIzLjYxODMgMTE1Ljc3IDIzLjg4MjZWMjMuOTE1MUMxMTUuNzcgMjQuMTgzOCAxMTUuNjk0IDI0LjM5MTggMTE1LjU0MiAyNC41MzkxQzExNS4zOTEgMjQuNjg2NSAxMTUuMTU5IDI0Ljc2MDEgMTE0Ljg0NyAyNC43NjAxWk0xMTkuNzk2IDMyLjA1MzFDMTE4LjgxMyAzMi4wNTMxIDExOC4wODIgMzEuNzkzMSAxMTcuNjA2IDMxLjI3MzFDMTE3LjEzMyAzMC43NTMxIDExNi44OTcgMzAuMDIzIDExNi44OTcgMjkuMDgyNlYyOC4zMDI2QzExNi44OTcgMjcuMzY2NiAxMTcuMTM2IDI2LjY0MDggMTE3LjYxMiAyNi4xMjUxQzExOC4wODkgMjUuNjA5NSAxMTguODE3IDI1LjM1MTYgMTE5Ljc5NiAyNS4zNTE2QzEyMC4wNTIgMjUuMzUxNiAxMjAuMjkgMjUuMzczMyAxMjAuNTExIDI1LjQxNjZDMTIwLjczNyAyNS40NTU2IDEyMC45NCAyNS41MDk4IDEyMS4xMjIgMjUuNTc5MUMxMjEuMzA5IDI1LjY0ODUgMTIxLjQ3MSAyNS43MjIxIDEyMS42MSAyNS44MDAxTDEyMS43NDYgMjcuMTk3NkMxMjEuNTM0IDI3LjA2MzMgMTIxLjI5NiAyNi45NTA2IDEyMS4wMzEgMjYuODU5NkMxMjAuNzcxIDI2Ljc2ODYgMTIwLjQ3IDI2LjcyMzEgMTIwLjEyOCAyNi43MjMxQzExOS41OSAyNi43MjMxIDExOS4xOTYgMjYuODYxOCAxMTguOTQ1IDI3LjEzOTFDMTE4LjY5OCAyNy40MTIxIDExOC41NzQgMjcuODEwOCAxMTguNTc0IDI4LjMzNTFWMjkuMDI0MUMxMTguNTc0IDI5LjU0NDEgMTE4LjcwMiAyOS45NDUgMTE4Ljk1OCAzMC4yMjY2QzExOS4yMTggMzAuNTA4MyAxMTkuNjE2IDMwLjY0OTEgMTIwLjE1NCAzMC42NDkxQzEyMC40OTYgMzAuNjQ5MSAxMjAuNzk5IDMwLjYwNTggMTIxLjA2NCAzMC41MTkxQzEyMS4zMjggMzAuNDI4MSAxMjEuNTc1IDMwLjMxNzYgMTIxLjgwNSAzMC4xODc2TDEyMS42NjggMzEuNTkxNkMxMjEuNDU2IDMxLjcxMyAxMjEuMTg5IDMxLjgxOTEgMTIwLjg2OSAzMS45MTAxQzEyMC41NDggMzIuMDA1NSAxMjAuMTkxIDMyLjA1MzEgMTE5Ljc5NiAzMi4wNTMxWk0xMjIuOTAyIDMxLjg5MDZWMjMuMjk3NkgxMjQuNTcyVjMxLjg5MDZIMTIyLjkwMlpNMTI5LjAzNCAzMi4wNDY2QzEyNy45NDIgMzIuMDQ2NiAxMjcuMTI5IDMxLjc5NTMgMTI2LjU5NiAzMS4yOTI2QzEyNi4wNjMgMzAuNzkgMTI1Ljc5NyAzMC4wNjIgMTI1Ljc5NyAyOS4xMDg2VjI4LjI3NjZDMTI1Ljc5NyAyNy4zMzIgMTI2LjA0NiAyNi42MDYxIDEyNi41NDQgMjYuMDk5MUMxMjcuMDQyIDI1LjU5MjEgMTI3Ljc2NiAyNS4zMzg2IDEyOC43MTUgMjUuMzM4NkMxMjkuMzU2IDI1LjMzODYgMTI5Ljg5MiAyNS40NTEzIDEzMC4zMjEgMjUuNjc2NkMxMzAuNzUgMjUuOTAyIDEzMS4wNyAyNi4yMjI2IDEzMS4yODMgMjYuNjM4NkMxMzEuNDk5IDI3LjA1MDMgMTMxLjYwOCAyNy41NDQzIDEzMS42MDggMjguMTIwNlYyOC4zNDgxQzEzMS42MDggMjguNTA0MSAxMzEuNTk5IDI4LjY2NDUgMTMxLjU4MiAyOC44MjkxQzEzMS41NjkgMjguOTg5NSAxMzEuNTQ5IDI5LjE0MTEgMTMxLjUyMyAyOS4yODQxSDEzMC4wMTVDMTMwLjAyOCAyOS4wNDU4IDEzMC4wMzUgMjguODIwNSAxMzAuMDM1IDI4LjYwODFDMTMwLjAzOSAyOC4zOTE1IDEzMC4wNDEgMjguMTk2NSAxMzAuMDQxIDI4LjAyMzFDMTMwLjA0MSAyNy43MjQxIDEyOS45OTMgMjcuNDcwNiAxMjkuODk4IDI3LjI2MjZDMTI5LjgwMyAyNy4wNTAzIDEyOS42NTggMjYuODkgMTI5LjQ2MyAyNi43ODE2QzEyOS4yNjggMjYuNjczMyAxMjkuMDE4IDI2LjYxOTEgMTI4LjcxNSAyNi42MTkxQzEyOC4yNjkgMjYuNjE5MSAxMjcuOTM5IDI2Ljc0MjYgMTI3LjcyNyAyNi45ODk2QzEyNy41MTUgMjcuMjM2NiAxMjcuNDA5IDI3LjU4NzYgMTI3LjQwOSAyOC4wNDI2VjI4LjYzNDFMMTI3LjQxNSAyOC44MjI2VjI5LjMyMzFDMTI3LjQxNSAyOS41MjI1IDEyNy40NDUgMjkuNzA2NiAxMjcuNTA2IDI5Ljg3NTZDMTI3LjU3MSAzMC4wNDQ2IDEyNy42NzcgMzAuMTkyIDEyNy44MjUgMzAuMzE3NkMxMjcuOTcyIDMwLjQzOSAxMjguMTY3IDMwLjUzNDMgMTI4LjQxIDMwLjYwMzZDMTI4LjY1NyAzMC42NzMgMTI4Ljk2NCAzMC43MDc2IDEyOS4zMzMgMzAuNzA3NkMxMjkuNzMxIDMwLjcwNzYgMTMwLjExIDMwLjY2NDMgMTMwLjQ3IDMwLjU3NzZDMTMwLjgzNCAzMC40ODY2IDEzMS4xNzkgMzAuMzY1MyAxMzEuNTA0IDMwLjIxMzZMMTMxLjM2MSAzMS41MjY2QzEzMS4wNyAzMS42ODcgMTMwLjcyOCAzMS44MTI2IDEzMC4zMzQgMzEuOTAzNkMxMjkuOTQ0IDMxLjk5OSAxMjkuNTEgMzIuMDQ2NiAxMjkuMDM0IDMyLjA0NjZaTTEyNi42ODEgMjkuMjg0MVYyOC4xNzkxSDEzMS4xODVWMjkuMjg0MUgxMjYuNjgxWk0xMzQuOTk0IDMyLjA0NjZDMTM0LjQ4MyAzMi4wNDY2IDEzNC4wMjggMzEuOTk2OCAxMzMuNjI5IDMxLjg5NzFDMTMzLjIzNSAzMS44MDE4IDEzMi44OTkgMzEuNjkzNSAxMzIuNjIxIDMxLjU3MjFMMTMyLjQ3MiAzMC4xMjkxQzEzMi44MDEgMzAuMjgwOCAxMzMuMTYzIDMwLjQxMyAxMzMuNTU3IDMwLjUyNTZDMTMzLjk1NiAzMC42MzgzIDEzNC4zOTQgMzAuNjk0NiAxMzQuODcgMzAuNjk0NkMxMzUuMjg2IDMwLjY5NDYgMTM1LjU4OCAzMC42NDI2IDEzNS43NzQgMzAuNTM4NkMxMzUuOTYgMzAuNDMwMyAxMzYuMDUzIDMwLjI3IDEzNi4wNTMgMzAuMDU3NlYzMC4wMTg2QzEzNi4wNTMgMjkuODc1NiAxMzYuMDEgMjkuNzU4NiAxMzUuOTIzIDI5LjY2NzZDMTM1Ljg0MSAyOS41NzY2IDEzNS42OTQgMjkuNDk0MyAxMzUuNDgxIDI5LjQyMDZDMTM1LjI2OSAyOS4zNDI2IDEzNC45NyAyOS4yNjAzIDEzNC41ODQgMjkuMTczNkMxMzQuMDUxIDI5LjA0OCAxMzMuNjI5IDI4LjkwMjggMTMzLjMxNyAyOC43MzgxQzEzMy4wMDkgMjguNTY5MSAxMzIuNzg4IDI4LjM2MzMgMTMyLjY1NCAyOC4xMjA2QzEzMi41MiAyNy44NzM2IDEzMi40NTIgMjcuNTc5IDEzMi40NTIgMjcuMjM2NlYyNy4xNzgxQzEzMi40NTIgMjYuNTc1OCAxMzIuNjY3IDI2LjEyMyAxMzMuMDk2IDI1LjgxOTZDMTMzLjUyNSAyNS41MTIgMTM0LjE2IDI1LjM1ODEgMTM1IDI1LjM1ODFDMTM1LjQ5OSAyNS4zNTgxIDEzNS45MzkgMjUuNDA4IDEzNi4zMiAyNS41MDc2QzEzNi43MDYgMjUuNjAzIDEzNy4wMjYgMjUuNzE3OCAxMzcuMjgyIDI1Ljg1MjFMMTM3LjQzMSAyNy4xNzgxQzEzNy4xMjggMjcuMDM1MSAxMzYuNzg4IDI2LjkxNiAxMzYuNDExIDI2LjgyMDZDMTM2LjAzNCAyNi43MjEgMTM1LjYyOSAyNi42NzExIDEzNS4xOTUgMjYuNjcxMUMxMzQuOTE0IDI2LjY3MTEgMTM0LjY5MSAyNi42OTUgMTM0LjUyNiAyNi43NDI2QzEzNC4zNjYgMjYuNzg2IDEzNC4yNTEgMjYuODQ4OCAxMzQuMTgxIDI2LjkzMTFDMTM0LjExMiAyNy4wMTM1IDEzNC4wNzcgMjcuMTEzMSAxMzQuMDc3IDI3LjIzMDFWMjcuMjYyNkMxMzQuMDc3IDI3LjM5MjYgMTM0LjExNCAyNy41MDUzIDEzNC4xODggMjcuNjAwNkMxMzQuMjY2IDI3LjY5NiAxMzQuNDA3IDI3Ljc4MjYgMTM0LjYxIDI3Ljg2MDZDMTM0LjgxNCAyNy45MzQzIDEzNS4xIDI4LjAxNDUgMTM1LjQ2OCAyOC4xMDExQzEzNi4wMDYgMjguMjEzOCAxMzYuNDM3IDI4LjM0NiAxMzYuNzYyIDI4LjQ5NzZDMTM3LjA4NyAyOC42NDkzIDEzNy4zMjMgMjguODQ0MyAxMzcuNDcgMjkuMDgyNkMxMzcuNjE4IDI5LjMxNjYgMTM3LjY5MSAyOS42MjQzIDEzNy42OTEgMzAuMDA1NlYzMC4wODM2QzEzNy42OTEgMzAuNzQyMyAxMzcuNDY4IDMxLjIzNDEgMTM3LjAyMiAzMS41NTkxQzEzNi41NzYgMzEuODg0MSAxMzUuOSAzMi4wNDY2IDEzNC45OTQgMzIuMDQ2NloiIGZpbGw9IiNGNkY2RjYiLz4KPC9zdmc+ClhAybDn2tK8ED02UtdSJzxV+NF3tMuGQLcvndUGObrYgXP9y2NBNV6vVsNmkNt4zDnadCx8uyAr93vIuAsBRMiUkA==" } } ``` * `id` : Unique identifier of this credential. * `encoded` : Encoded version of the credential, represented as a base64 string. * `decoded` : Decoded version of the credential: * `namespaces` : Each namespace corresponds to a group of claims included in the credential. These can be claims that are part of a specific standard, jurisdiction or any other reference. * `org.iso.18013.5.1` : This namespace includes claims that comply with the [ISO/IEC 18013-5 standard](https://www.iso.org/standard/69084.html). Each element in this object represents a claim and includes the following fields: * `digestID` : Serial number identifying the claim within this namespace. * `random` : This is the random value (nonce/salt) added to each claim when it is encoded. This enables [unwanted disclosures protection](#salt-values-support-unwanted-disclosures-protection). * `elementIdentifier` : The identifier of this claim as per [ISO/IEC 18013-5 standard](https://www.iso.org/standard/69084.html). * `elementValue` : * `type` : Claim data type. * `value` : Claim value. * `issuerAuth` : This is the digital signature represented as a COSE\_sign1 structure. It includes different details regarding the encrypton algorithm, certificates and public keys. ## Branded mDoc [#branded-mdoc] The following image depicts how the credential above would look like in the holder's digital wallet: Branded credential # MATTR GO Changelog URL: /docs/resources/changelog/go [Subscribe](/docs/resources/changelog/newsletter-signup) to our release newsletter to get the latest updates on product releases, new features and bug fixes. ## Default engagement method and verification improvements in MATTR GO Verify 2026-06-22 Tags: GO Verify This release (v1.9.0) of the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started) lets you choose how verifications start and includes a range of fixes and improvements: * **Choose your default engagement method**: You can now set the default method used to start a verification, either QR code or NFC. This setting applies only to devices that support NFC, such as supported Android devices. If you do not set a default, NFC-capable devices use NFC by default while all other devices continue to use QR code engagement. * **Verification template icon fix**: We have fixed an issue on the verification template details screen where a placeholder icon could appear instead of the icon configured for the template. The screen now shows the correct icon for each verification template. * **Clearer text for hidden claims**: We have improved the default message shown when claims returned by the holder are hidden after a failed verification, so it more clearly explains why those claims are not displayed. * **Logging improvements**: We've continued to improve the internal logging functionality of our app, making it easier to debug if required. The MATTR GO Verify example app can be branded and configured to meet all your production verification needs. Check out our [MATTR GO Verify onboarding guide](/docs/verification/go-verify/getting-started) for more information. ## Verification checks, expected values, and approved purposes in MATTR GO Verify 2026-06-09 Tags: GO Verify This release (v1.8.0) of the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started) introduces new ways to define what a successful verification means and a refreshed experience to match: * **Use cases are now verification checks**: Use cases have been renamed to verification checks to better reflect how they support real-world credential verification. A verification check can now assess whether a credential is both valid and acceptable for a specific purpose, helping users make clearer decisions based on your requirements. * **Expected values**: You can now configure expected values within verification check templates to define the exact claim values required for a successful outcome, such as confirming that an `is_over_18` claim is set to `true`. * **Approved purposes**: Approved purposes give you greater control over which issuers are acceptable for a verification check. When enabled, only credentials issued by approved issuers for that purpose are accepted, even if credentials from other issuers are otherwise valid. * **Refreshed home and result screens**: The home screen now groups a check that can be satisfied with multiple doc types into a single entry, with users able to select supported doc types from within the check. Result screens have been updated to better reflect both credential validity and acceptability, with clearer messaging, improved error visibility, and more control over whether claim values are shown on invalid result screens. When expected values or approved purposes are used, results are simplified to Approved or Not approved, with intermediate states such as unable to verify, revoked, or not yet active treated as not approved for a clearer outcome. * **Customizable result messaging**: Verification checks that use expected values or approved purposes can now display custom result headings, configured through your language pack. Each verification check can also include its own success and failure hint text to provide contextual guidance after a result, while maintaining backwards compatibility when no hint text is configured. The MATTR GO Verify example app can be branded and configured to meet all your production verification needs. Check out our [MATTR GO Verify onboarding guide](/docs/verification/go-verify/getting-started) for more information. ## Return to wallet 2026-04-30 Tags: GO Hold Version 3.8.0 of the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) is now available in the app stores. Based on feedback, we have removed the "Return to wallet" button during same-device presentations. This change reduces confusion and helps prevent users from accidentally exiting the flow before completing their presentation. The result is a more streamlined and reliable user experience. **Bug fixes and under-the-hood improvements** We have also made several improvements to enhance stability, security, and the overall user experience, including: * Fixes an issue that caused initialization issues on iOS devices as a result of app pre-warming. * Fixes to auto-delete settings to ensure expected behaviour. * Fixes for broken links within the app. * Further enhancements to how on-device logs are generated and secured. * The iOS app is now being built using iOS 26 SDK, aligning with Apple's latest App Store Connect. This has no effect on the minimum version required to run the app (iOS 15 or higher). ## Status List support update in GO Verify 2026-04-13 Tags: GO Verify This release (v1.7.0) of the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started) adds support for a newer version of the Token Status List specification: * **Token Status List Draft 14 support**: The app can now check the revocation status of credentials issued using the [Token Status List Draft 14](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-14) specification while maintaining existing support for credentials issued using Draft 3. ## Stability and visual improvements in GO Verify 2026-02-27 Tags: GO Verify This release (v1.6.0) of the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started) introduces improvements to stability and user interface consistency: * **NFC stability improvements**: Changes have been made to improve general stability, especially when using NFC to request a credential. * **Improved visual consistency**: We have fixed a number of visual inconsistencies on the Privacy Policy, Terms of Use, Settings and Trusted Issuer pages. ## Improved notifications and debugging in GO Hold 2026-02-23 Tags: GO Hold Version 3.7.0 of the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) is now available in the app stores. This release improves the reliability of push notifications and how the app responds when you interact with them. Tapping a notification when the app is closed will now more consistently take you to the relevant screen, rather than simply opening the app. ## Camera performance improvements in GO Verify 2025-12-09 Tags: GO Verify This release (v1.5.1) of the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started) introduces focused improvements to camera performance and reliability: * **Faster camera startup**: Under-the-hood optimizations have significantly reduced camera initialization time, ensuring verification workflows begin more quickly. * **More responsive camera switching**: Switching between front and rear cameras now happens more smoothly, reducing delays during verification. * **Improved torch reliability**: The torch toggle now operates more consistently, automatically turning off when the app moves to the background or when switching from rear to front camera. These enhancements ensure a more reliable and responsive verification experience across different lighting conditions and use cases. ## Enhanced user experience and performance in GO Hold 2025-11-25 Tags: GO Hold Version 3.6.0 of the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) is now available in the app stores. This release introduces streamlined navigation, improved credential claiming, and foundational performance enhancements. Highlights include: * **Simplified post-claim navigation**: After claiming a credential, users are now taken directly to the Wallet screen, where the new credential appears. This creates a more intuitive flow and helps users quickly access their newly issued credentials. * **Seamless claim experience**: Users can now start the credential issuance flow from outside the app by scanning a QR code with their device's native camera or following deep links. This eliminates friction and makes it easier to begin the credential claiming journey. * **Architecture and performance improvements**: This release includes foundational updates to enhance stability, compatibility, and overall app performance, ensuring a reliable experience across devices. ## MATTR GO Verify 1.5.0 2025-11-14 Tags: GO Verify This release (v1.5.0) of the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started) introduces several enhancements designed to make verifying digital credentials clearer, faster, and more intuitive: * **Refined verification results**: Credential details now take center stage, with the portrait image displayed prominently for improved visual context. * **Improved readability**: The Privacy Statement and Terms of Use now feature responsive text sizing, ensuring a seamless reading experience across all devices. * **React Native architecture upgrade**: Enabled the new React Native architecture to support enhanced performance and prepare the foundation for future capabilities. * **Bug fixes and enhancements**: Various bug fixes and improvements to ensure continued reliability and optimal performance. ## Improved handling of untrusted issuers in MATTR GO Verify 2025-10-01 Tags: GO Verify This release (v1.4.0) of the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started) introduces improved handling of credentials from untrusted issuers, along with infrastructure upgrades and various bug fixes and user experience improvements. * **Improved handling of untrusted issuers**: MATTR GO Verify now shows an explicit warning if the issuer of a credential presented for verification has certificates that are unknown, expired, or not yet valid. In these cases, all claims are blurred by default and an explicit warning is shown. Users can still review the claims by tapping “Show Claims” within the warning. This change helps protect against accidental exposure to potentially fraudulent credentials, while ensuring claims remain accessible for informed review when needed. * **Minor fixes and UX improvements**: Various bug fixes and user experience enhancements ensure smoother operation across verification workflows. ## Enhanced issuance and performance in GO Hold 2025-09-12 Tags: GO Hold Version 3.5.0 of the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) is now available in the app stores. This release introduces updates and enhancements designed to support smooth and flexible digital credential experiences. Highlights include: * **Support for OID4VCI Pre-Authorized Code flow**: Introducing support for the [OID4VCI Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview), holders can now receive credentials directly from issuers without needing to authenticate at the moment of issuance. This enables simpler, low-friction credential journeys while maintaining strong security. * **Infrastructure upgrades**: Improvements under the hood provide better app performance today and strengthen the foundations for future features. * **General bug fixes and stability improvements**: Ongoing updates ensure the app continues to run reliably in production environments. These improvements further streamline the user experience and make it easier for organizations to deliver trusted digital credential journeys at scale. ## Smarter verification, greater flexibility in GO Verify 2025-07-01 Tags: GO Verify This release (v1.3.1) of the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started) introduces new features that improve verification flexibility and clarity: * Dynamic verification presets: GO Verify can now retrieve the latest verification presets when online, then cache them for offline use. * Dynamic trusted issuer list: Trusted issuer information is now dynamically updatable, removing the need for app releases when changes are needed. This allows for faster trust list management and greater responsiveness to trust network updates. * Clearer verification results: We've updated the copy on the results screen to show both the credential’s status and a summary of the verification result, enhancing transparency and helping users confidently interpret outcomes. * NFC support for Android: GO Verify now supports NFC device engagement for mDocs in-person verification on Android devices. BLE continues to handle data transfer, while NFC enables intuitive tap-to-verify experiences. The MATTR GO Verify example app can be branded and configured to meet all your production verification needs. Check out our [MATTR GO Verify onboarding guide](/docs/verification/go-verify/getting-started) for more information. ## Improved credential status messages in GO Hold 2025-06-30 Tags: GO Hold Version 3.4.0 of the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) is now available in the app stores. This release introduces improved credential status messaging to enhance user clarity and trust, along with general stability and security improvements. Users will now see clearer, more informative messages when a credential is: * Temporarily or permanently revoked * Issued by an untrusted source * Expired or not yet active * Of an unsupported type These updates help users quickly understand the status of their credentials and take appropriate action. This version also includes various bug fixes and security enhancements to ensure a smoother and more secure experience. ## mDocs revocation support in GO Hold 2025-03-20 Tags: GO Hold Version 3.3.1 of the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) is now live in the app stores. This release introduces support for [mDocs revocation](/docs/issuance/revocation/overview), enabling the app to perform status checks on any claimed credentials. Users can now easily view the status of their mDocs and see whether they are active or have been revoked, providing full transparency and control over their credentials. The MATTR GO Hold example app can be branded and configured to meet all your production credential holding needs. Check out our [MATTR GO Hold onboarding guide](/docs/holding/go-hold/getting-started) for more information. ## mDocs revocation support in GO Verify 2025-03-12 Tags: GO Verify This release (v1.2.0) of the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started) introduces support for [mDocs revocation](/docs/issuance/revocation/overview). You can configure the app to perform mDocs status checks as part of the verification workflow, ensuring revoked mDocs are not verified. The MATTR GO Verify example app can be branded and configured to meet all your production verification needs. Check out our [MATTR GO Verify onboarding guide](/docs/verification/go-verify/getting-started) for more information. ## Enhanced privacy and security in GO Verify 2025-01-21 Tags: GO Verify This release (v1.1.3) of the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started) enhances security and privacy by blocking devices from taking screenshots or recording the verification results page. This mitigates the risk of presenting outdated proof of verification, as well as prevents sensitive information from being unintentionally or maliciously captured and shared. The MATTR GO Verify example app can be branded and configured to meet all your production verification needs. Check out our [MATTR GO Verifier onboarding guide](/docs/verification/go-verify/getting-started) for more information. ## Online presentation available in GO Hold 2024-12-12 Tags: GO Hold The latest version (v3.2.0) of the **MATTR GO Hold example app** is now available, introducing support for [online presentation](/docs/verification/remote-overview) of [mDocs](/docs/concepts/mdocs) as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html). Refer to the example app [getting started](/docs/holding/go-hold/getting-started) guide to try this new capability with a number of MATTR Labs demos. ## Fixing configuring available credential formats in GO Hold applications 2024-11-12 Tags: GO Hold This release (v3.1.2) of the **MATTR GO Hold example app** includes a bug fix that enables configuring available credential formats in different wallets. While the MATTR GO Hold example app supports all available MATTR credential formats (mDocs, CWT and JSON), any branded GO Hold applications can choose to only support some of these available formats. This fix now ensures that any UI elements relevant to specific credential formats are not displayed when these formats are not supported in a branded GO Hold application. The MATTR GO Hold example app can be branded and configured to meet all your production credential holding needs. Check out our [MATTR GO Hold onboarding guide](/docs/holding/go-hold/getting-started) for more information. ## Introducing the MATTR GO Hold example app 2024-10-24 Tags: GO Hold We are releasing a new version (v3.1.1) of the app formerly known as the **MATTR Showcase wallet**, renaming it to the **MATTR GO Hold example app**. Built using the MATTR Pi wallet SDK, this app showcases everything the SDK has to offer. The MATTR GO Hold example app can be branded and configured to meet all your production credential holding needs. Check out our [MATTR GO Hold onboarding guide](/docs/holding/go-hold/getting-started) for more information. ## Terminology updates across MATTR platforms 2024-10-15 Tags: GO Hold, GO Verify To make it easier to consume our capabilities, we have made some changes to our terminology to describe credentials supported by MATTR platforms by their underlying technology and standards. The following credential formats are supported: * [mDocs](/docs/concepts/mdocs): Previously referred to as Mobile credentials. * [CBOR Web Tokens (CWT) credentials](/docs/concepts/cwt): Previously referred to as Compact credentials. * JSON credentials: Previously referred to as Web credentials. ## Introducing the MATTR GO Verify example app 2024-09-26 Tags: GO Verify The first version (v1.0.0) of the MATTR GO Verify example app is now available in the app stores. This app is built using the [MATTR Pi Verifier SDKs](/docs/verification/sdks/overview) and demonstrates the full range of MATTR in-person verification capabilities: * **[mDocs](/docs/concepts/mdocs) verification**: Request and verify mDLs and mdocs as per ISO/IEC 18013-5 and ISO/IEC TS 23220-4. * **[CWT credentials](/docs/concepts/cwt) verification**: Instantly verify a CWT credential by scanning a QR code displayed by the holder. * **Real-time results**: View verification outcomes along with relevant credential information. #### Key Functionalities [#key-functionalities] * **Pre-configured setup**: * Request templates to showcase verification capabilities in non-production use cases, including support for verification of [mDocs](/docs/concepts/mdocs) and [CWT credentials](/docs/concepts/cwt). * Fixed list of trusted issuers. Credentials issued by other entities cannot be verified by this example app. * **Customizable display**: Tailor the result screen to highlight either verification status or detailed credential information. * **Auto-hide results**: Configure the app to automatically hide verification results after a set period for enhanced privacy. * **Optimized scanning**: Use flashlight and reverse camera functions to improve scanning in different environment conditions. * **Privacy-preserving**: No data is persisted, and credential information is only visible for a limited time required to complete the verification workflow. The MATTR GO Verify example app can be branded and configured to meet all your production verification needs. Check out our [MATTR GO Verifier onboarding guide](/docs/verification/go-verify/getting-started) for more information. ## Improved user experience in the MATTR Showcase and GO Wallets 2024-08-20 Tags: GO Hold The latest version (v3.0.4) of our MATTR Showcase Wallet is out, with an improved user experience across the application. #### Key Features [#key-features] * Revamped header and footer navigation menus offer improved usability and maximize screen view area. * New options for the user to interact with verification requests over in-person and remote channels. * Improved control during credential presentation flows when providing consent to sharing user information. * Enhanced security via configurable restrictions over inline and remote contexts for Web semantic credentials. * Upgrade to the latest MATTR Pi SDK with support for React Native 0.72. **MATTR GO Wallet** customers will receive a new release which includes all the new features and enhancements from our latest MATTR Showcase Wallet version. ## Clock skew tolerance in the MATTR Showcase Wallet 2024-06-25 Tags: GO Hold The latest version (v2.9.11) of our MATTR Showcase Wallet better supports in-person Mobile Credentials (mDL/mDoc) interactions by introducing clock skew tolerance to accommodate time inconsistencies between the wallet and entities it is interacting with. ## Enhancements to MATTR Showcase Wallet and MATTR GO Wallet 2024-04-02 Tags: GO Hold The latest version (v2.9.1) of our MATTR Showcase Wallet is out, expanding our supported credential formats and offering several functional and usability enhancements. #### Key Features [#key-features] * Users can now claim and manage Mobile Credentials issued through the [OID4VCI workflow](/docs/issuance/oid4vci-overview). * Users can now respond to Mobile Credential presentation requests sent by verifiers. * Users will now use their device authentication capabilities instead of the app authentication when accessing the MATTR Showcase Wallet. * Users can now easily assess the status of their credential’s validation outcome using a newly introduced credential pill status. Clicking the pill provides further validation details. **MATTR GO Wallet** customers will receive a new release which includes all the new features and enhancements from our latest MATTR Showcase Wallet version. ## Enhancements to the MATTR Showcase Wallet 2024-02-08 Tags: GO Hold The latest version (v2.8.6) of our MATTR Showcase Wallet is out! Our latest MATTR Showcase Wallet release is mainly a technical release to improve technical stability and consistency, but we couldn't help but include some wallet improvements along the way. #### Key Features [#key-features] * Migration to the latest wallet service. * Upgrade to the latest wallet SDK. * Miscellaneous usability enhancements and bug fixes. ## Introducing MATTR GO Verifier 2024-02-05 Tags: GO Verify Getting started with credential verification just got easier with the launch of the new MATTR GO Verifier. No need to develop your own credential verification app when you can customize ours with your brand and business rules whilst leveraging MATTR’s world-class technology and expertise. Available for both iOS and Android, the [MATTR GO Verifier](https://files.mattr.global/specsheets/go-verify.pdf) joins [MATTR GO Wallet](https://files.mattr.global/specsheets/go-hold.pdf) as part of the [MATTR GO platform](https://mattr.global/platforms/go). These ready-to-go white label products are designed to accelerate your credential verification journey by eliminating the cost, time and complexity of developing your own apps from scratch. [Contact us](https://mattr.global/contact-us) today to learn how easy it is to get going with credentials verification using our world-class MATTR GO toolset. ## Enhancements to MATTR Showcase Wallet and MATTR GO Wallet 2024-01-10 Tags: GO Hold The latest version (v2.8.5) of our MATTR Showcase Wallet is out! To provide a better user experience and application performance when wallets include multiple credentials and pending requests, our latest MATTR Showcase Wallet release includes several usability enhancements and bug fixes. #### Key Features [#key-features] * Users can now filter their activity feed so that only messages that require attention are displayed. For example, this can help users identify pending credential offers. * Improved wallet performance as credential offers are now only validated when the user selects to accept the offer. * Improved performance as credential status is no longer displayed on the credential activity card. **MATTR GO Wallet** customers will receive a new release which includes all the new features and enhancements from our latest MATTR Showcase Wallet version. ## Enhancements to MATTR Showcase Wallet and MATTR GO Wallet 2023-11-16 Tags: GO Hold The latest version (v2.8.4) of our MATTR Showcase Wallet is out! To provide a better user experience, our latest MATTR Showcase Wallet release includes some usability enhancements, bug fixes and copy changes to accompany the enhancements. #### Key Features [#key-features] * Customers can configure if auto-deletion of expired credentials and its associated activities is turned on or off for the wallet. * Users can set if auto-deletion of expired credentials and its associated activities is enabled or not. Users can also set the frequency with which auto-deletion checks are done. **MATTR GO Wallet** customers will receive a new release which includes all the new features and enhancements from our latest MATTR Showcase Wallet version. ## Enhancements to MATTR Showcase Wallet and MATTR GO Wallet 2023-10-25 Tags: GO Hold The latest version (v2.8.1) of our MATTR Showcase Wallet is out! To provide a better user experience when wallets include multiple credentials, our latest MATTR Showcase Wallet release includes several usability enhancements, bug fixes and copy changes to accompany the enhancements. #### Key Features [#key-features] * Users can sort their list of credentials by collection or expiry dates. * Users can filter their list of credentials by their status. Expired credentials are hidden by default, but users can configure their wallet to show them when required. * Users can immediately accept credential offers. * Users remain on the Wallet tab when a credential is deleted. * Share button is only available for credentials that can share a QR code. * Activity details are auto-refreshed. **MATTR GO Wallet** customers will receive a new release which includes all the new features and enhancements from our latest MATTR Showcase Wallet version. ## MATTR Wallet and MATTR GO Wallet now support the BBS2022 signature suite 2023-08-15 Tags: GO Hold The latest version (v2.7.1) of our MATTR Wallet is out! #### Key Features [#key-features] * To leverage its key security and efficiency enhancements, our MATTR Wallet now supports the BBS2022 signature suite across the credential lifecycle (issue, hold, and present). * This version also includes a migration to React Native V0.71, as well as a number of UI and usability enhancements. **MATTR GO Wallet** customers will receive a new release which includes all the new features and enhancements from our latest MATTR Wallet version. ## Introducing MATTR GO: Our nimble and robust white-label apps platform 2023-08-03 Tags: GO Hold, GO Verify We are excited to announce the official launch of our MATTR GO platform, which provides customers with a fast and convenient way of creating a white-label branded apps for use in their credential ecosystems. Built on our robust MATTR Pi Wallet Toolkit, the MATTR GO Wallet leverages all the benefits of our MATTR Wallet while allowing customers to wrap the interface in a brand their users already know and trust. It is interoperable and compliant with global standards, and can be integrated with existing MATTR’s credential lifecycle capabilities, available in the MATTR VII Platform. The MATTR GO Wallet is suitable for customers looking for a ready-to-use wallet that can be distributed directly to credential holders in their ecosystem, with no coding required. [Find out more](https://mattr.global/platform/go/) and refer the [Product Specification Sheet](https://files.mattr.global/specsheets/go-hold.pdf) for further details. ## New Features and Enhancements across MATTR platforms 2023-08-03 Tags: GO Hold, GO Verify We are happy to announce several key improvements across our MATTR platforms, highlighted by new self-service capabilities, enhanced APIs, and several MATTR Wallet features: ### Self-service Tenant Management [#self-service-tenant-management] Our new suite of API endpoints support the ability to manage tenants (create, view and delete) and analytics events in your environment. Please [contact us](mailto:dev-support@mattr.global) for more information and/or access to this new capability. ### Preventing Issuance of Expired Credentials [#preventing-issuance-of-expired-credentials] Our MATTR VII credential issuance API endpoints now prevent issuing any expired credentials where the current date has passed the specified expiry date. ### MATTR Wallet and MATTR GO Release [#mattr-wallet-and-mattr-go-release] Our recent MATTR Wallet release (V2.6.1) introduces support for claiming and storing Compact Credentials issued through the OID4VCI protocol. In addition, it includes several UI enhancements to further improve the wallet user experience. **MATTR GO Wallet** customers will receive a new release which includes all new features and enhancements from our latest MATTR Wallet version. ## MATTR Wallet - Migration to React Native V0.70 2023-03-23 Tags: GO Hold The latest release of our MATTR Wallet is out! Our MATTR Wallet has been migrated to React Native V0.70 as well as being upgraded to Realm V11 to leverage key improvements with these versions. This release also includes a number of UI and usability enhancements. Refer to the [React Native](https://reactnative.dev/blog/2022/09/05/version-070) and [MongoDB](https://www.mongodb.com) pages for further details on React Native V0.70 and Realm V11. ## MATTR Wallet - Additional Language Support 2022-12-16 Tags: GO Hold Our MATTR Wallet now supports Swiss German and French Canadian languages. To experience the MATTR Wallet in these languages, you will need to set the correct language on your iPhone or Android device. Refer to the [Apple](https://support.apple.com/en-nz/HT204031) and [Google](https://support.google.com/accounts/answer/32047?hl=en\&co=GENIE.Platform%3DAndroid) support pages if you need to change your language settings. ## MATTR Wallet - New look and feel 2022-09-30 Tags: GO Hold Our new MATTR Wallet is up! Experience a brand new look and feel! ### Improved journeys and flow [#improved-journeys-and-flow] A revised navigation and structure allows you to access all the features you need in a fast and efficient way. ### New and improved onboarding [#new-and-improved-onboarding] New experience to guide and inform you on how to get the most out of your wallet. ### Credentials look more life-like to build trust with your users. Your credentials are our top priority! [#credentials-look-more-life-like-to-build-trust-with-your-users-your-credentials-are-our-top-priority] Your credentials have been redesigned to look and feel like physical cards users are already familiar with. ### Create credentials that match your organizations branding [#create-credentials-that-match-your-organizations-branding] Credentials mimic real-world cards more than ever before. You can now customize colors, logos and include watermarks to suit your brand needs. ### Log of all your information and sharing events plus activities [#log-of-all-your-information-and-sharing-events-plus-activities] A log of all credential interactions you have performed with the MATTR Wallet, along with different ways to view the events, such as grouping by connection. # MATTR Changelogs URL: /docs/resources/changelog Changelog for our MATTR VII API platform. Changelog for our MATTR Pi SDKs. Changelog for our MATTR GO whitelabel apps. Sign up for our newsletter to stay updated on the latest releases. # Release newsletter signup URL: /docs/resources/changelog/newsletter-signup Use the form below to sign up for our release newsletter and stay updated with the latest features and improvements. # MATTR Pi Changelog URL: /docs/resources/changelog/pi [Subscribe](/docs/resources/changelog/newsletter-signup) to our release newsletter to get the latest updates on product releases, new features and bug fixes. ## SDK Tethering and breaking changes in the Android mDocs Verifier SDK 2026-06-26 Tags: Verifier Mobile, Android This major release (v7.0.0) of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview#android) introduces SDK Tethering, along with a number of breaking API changes and bug fixes. Because this release contains breaking changes, review the [Android 7.0.0 migration guide](/docs/verification/remote-mobile-verifiers/sdks/android-7.0.0-migration-guide) before upgrading. *Breaking changes* * **SDK Tethering**: The SDK now registers each app instance with your MATTR VII tenant, giving you operational insight into registered and active app instances and establishing a remote management channel we expect to extend in future releases. SDK Tethering is now required: `MobileCredentialVerifier.initialize` requires a `PlatformConfiguration` (previously optional) to configure the MATTR VII tenant used for registration and licensing. `initialize` can now throw `InvalidLicenseException` and `FailedToRegisterException`, and the majority of the SDK's APIs throw `InvalidLicenseException` when a valid license is not present. The SDK uses [Key Attestation](https://source.android.com/docs/security/features/keystore/attestation) during registration. See the [SDK Tethering guide](/docs/verification/remote-mobile-verifiers/sdks/sdk-tethering) for details. * **Renamed revocation status list APIs**: The revocation status list management API has been renamed from `TrustedIssuer`-prefixed terminology to `Revocation` terminology. `updateTrustedIssuerStatusLists` is now `refreshRevocationStatusLists`, `getTrustedIssuerStatusListsCacheInfo` is now `getRevocationStatusListsCacheInfo`, and `TrustedIssuerStatusListsCacheInfo` is now `RevocationStatusListsCacheInfo`. Update all references to the new names. * **Result types are now sealed interfaces**: `UpdateTrustedIssuerStatusListsResult` has been renamed to `RevocationStatusListsRefreshResult` and, along with `OnlinePresentationSessionResult`, converted from data classes to sealed interfaces with `Success` and `Failure` variants. The `.success: Boolean` field has been removed. Callers must replace field-based branching with `when`/`is` pattern matching. * **`MobileCredentialResponse` arrays are now required**: `MobileCredentialResponse.credentials` and `MobileCredentialResponse.credentialErrors` are now required non-nullable `List` fields and must be provided at construction. Callers can remove null-check branches and `?.` navigation when accessing these fields. * **Application ID parameter removed**: `MobileCredentialVerifier.requestMobileCredentials` no longer requires an `applicationId` parameter. The SDK now uses the application ID provided in `PlatformConfiguration` during initialization. Remove the `applicationId` argument and supply it via `PlatformConfiguration` instead. *Bug fixes* * Added size validation to `IssuerSigned.random` values, rejecting oversized values that could otherwise cause excessive memory use when verifying a presented credential. * Fixed an issue where long Status List distribution URLs would fail to parse, resulting in the SDK not being able to download and use the list. For a complete description of the changes, see the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/). *End of Life notice* * In line with our SLA, major version `6.x.x` of the Android mDocs Verifier SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **26 September 2026**, after which it will no longer be supported. ## SDK Tethering and breaking changes in the iOS mDocs Verifier SDK 2026-06-26 Tags: Verifier Mobile, iOS This major release (v6.0.0) of the [iOS mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview#ios) introduces SDK Tethering, along with a number of breaking API changes, smaller enhancements, and bug fixes. Because this release contains breaking changes, review the [iOS 6.0.0 migration guide](/docs/verification/remote-mobile-verifiers/sdks/ios-6.0.0-migration-guide) before upgrading. *New features* * **Automatic challenge generation for remote mobile verification**: The `challenge` parameter on `requestMobileCredentials` for remote mobile (app-to-app) verification is now optional. When omitted or blank, the SDK generates a cryptographically secure random 32-byte challenge automatically, removing the requirement for the caller to manage challenge generation. * **Connectivity error reporting for remote mobile verification**: `requestMobileCredentials` for remote mobile (app-to-app) verification now throws `MobileCredentialVerifierError.connectivityError` if the internet connection is lost during app-to-app verification. *Breaking changes* * **SDK Tethering**: The SDK now registers each app instance with your MATTR VII tenant, giving you operational insight into registered and active app instances and establishing a remote management channel we expect to extend in future releases. SDK Tethering is now required: `platformConfiguration` on `initialize` is mandatory and drives registration and licensing. `initialize` can now throw `MobileCredentialVerifierError.invalidLicense` and `MobileCredentialVerifierError.failedToRegister`, and the majority of the SDK's APIs throw `MobileCredentialVerifierError.invalidLicense` when a valid license is not present. The SDK uses [App Attest](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity) during registration where available. See the [SDK Tethering guide](/docs/verification/remote-mobile-verifiers/sdks/sdk-tethering) for details. * **Asynchronous initialization**: The `initialize` method is now asynchronous and must be awaited from an asynchronous context. * **Renamed and restructured types**: Several types and properties were renamed or restructured for cross-platform consistency. `VerificationResult` is now `MobileCredentialVerificationResult` and its `reason` property is now `failureType`, `TrustedCertificateVerificationResult.reason` is now `failureType`, and the now-removed `VerificationFailedReason` wrapper has been replaced by directly typed failure types. The revocation status list APIs were renamed from `TrustedIssuer`-prefixed terminology to `Revocation` terminology: `updateTrustedIssuerStatusLists()` is now `refreshRevocationStatusLists()` and `getTrustedIssuerStatusListsCacheInfo()` is now `getRevocationStatusListsCacheInfo()`, with their result and cache-info types renamed to match. * **Result types are now enums**: `RevocationStatusListsRefreshResult` and `OnlinePresentationSessionResult` have been converted from structs with optional properties to `@frozen` enums with `success` and `failure` cases. Callers must replace property-based branching with `switch`/`case` pattern matching. * **Serialization changes**: `MobileCredentialVerificationFailureType` and `TrustedCertificateVerificationFailureType` now encode and decode as a `{type, message}` object instead of a plain raw-value string. Update any code that persists or transports these values. * **`applicationId` parameter removed**: The `applicationId` parameter has been removed from `fetchAppleWalletConfiguration` and `requestMobileCredentials`. The SDK now uses the `applicationId` from the `PlatformConfiguration` provided at initialization. Update all call sites to the new method signatures. * **Removed error case**: `MobileCredentialVerifierError.platformConfigurationInvalid` has been removed and is no longer thrown by `fetchAppleWalletConfiguration`. *Bug fixes* * Fixed an issue where the verification result was not delivered during a proximity presentation when the holder terminated the session immediately after sending its response (for example, with auto-terminate enabled). The SDK now waits for response processing to complete before handling the BLE disconnect. * Fixed an issue where background prewarming could leave the SDK unable to initialize. The SDK now detects and recovers from stale storage keys automatically. For a complete description of the changes, see the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog). *End of Life notice* * In line with our SLA, major version `5.x.x` of the iOS mDocs Verifier SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **26 September 2026**, after which it will no longer be supported. ## Bug fixes in the React Native mDocs Holder SDK 2026-06-25 Tags: Holder, React Native This release (v9.0.4) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview) includes the following fixes: * On Android, fixed an issue where the SDK could crash if DCM configuration cleanup failed. Cleanup errors are now caught and logged, allowing the host app to continue safely. * On iOS, the SDK now recovers automatically from a storage key mismatch without data loss. A stored storage key could become stale for a number of reasons, leaving the SDK unable to initialize against existing credential data. This was most commonly observed during background prewarming, where the SDK could launch before the device's first unlock and be unable to read existing keychain items. On initialization, the SDK verifies the active storage key by attempting to decrypt an encrypted on-disk probe. If the recorded key is stale, the SDK enumerates all available keychain candidates, identifies the one that successfully decrypts the probe, updates the stored key, and removes the stale entry. If the probe cannot be read because of filesystem protection, `StorageInitializedInBackground` is thrown. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html#md:change-log). ## Status list URL parsing fix in the Android mDocs Verifier SDK 2026-06-24 Tags: Verifier Mobile, Android Version 6.1.1 of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview#android) is a bug fix release. *Bug fixes* * Fixed an issue where long status list distribution URLs would fail to parse, preventing the SDK from downloading and using the list. For complete details and technical guidelines, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/). ## React Native SDKs EOL announcement 2026-06-23 Tags: Holder, Verifier Mobile, React Native In line with our SLA, the following SDK versions have now reached their **End of Life (EOL)** and are no longer supported: * React Native mDocs Holder SDK v8. * React Native mDocs Verifier SDK v8. For a list of actively supported versions, please refer to the respective SDK documentation: * [Holder SDKs](/docs/holding/sdk-overview#versions) * [Verifier Mobile SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview#versions) If you have questions about upgrading or need support with migration, please [get in touch](mailto:dev-support@mattr.global). ## SDK Tethering, Wallet Attestation, and breaking changes in the Android mDocs Holder SDK 2026-06-22 Tags: Holder, Android This major release (v7.0.0) of the [Android mDocs Holder SDK](/docs/holding/sdk-overview#android) introduces SDK Tethering and Wallet attestation, along with a number of breaking API changes, a smaller enhancement, and bug fixes. Because this release contains breaking changes, review the [Android 7.0.0 migration guide](/docs/holding/sdk-operations/migration-guides/android-7.0.0-migration-guide) before upgrading. *New features* * **SDK Tethering**: The SDK can now register each app instance with your MATTR VII tenant, giving you operational insight into registered and active app instances and establishing a remote management channel we expect to extend in future releases. Tethering is enabled by passing a new optional `PlatformConfiguration` to `initialize`. It is currently optional but will become required in an upcoming release. See the [SDK Tethering guide](/docs/holding/sdk-operations/sdk-tethering) for details. * **Wallet attestation**: You can now build holder applications that claim credentials from issuers which restrict issuance to trusted wallet applications. When an issuer requires it, the SDK automatically proves the application's authenticity following the OAuth 2.0 Attestation-Based Client Authentication specification. Wallet attestation requires SDK Tethering. See the [Wallet attestation guide](/docs/holding/credential-claiming-guides/wallet-attestation) for details. * **Stronger application identity during issuance**: Pre-authorized issuance flows now pass the application's `client_id`, improving compatibility with issuers applying stricter controls. *Breaking changes* * **Configurable biometric authentication behavior**: A new `userAuthenticationType` parameter on `UserAuthenticationConfiguration` lets you configure biometric authentication behavior. `initialize` may now throw if `BiometryCurrentSet` is used with `OnInitialize`. * **Renamed and restructured types**: Several types and properties were renamed or restructured for cross-platform consistency, including `AuthenticationOption` (now `DeviceAuthenticationOption`), the `OfferedCredential.doctype` field (now `docType`), and the matched-credentials accessor on `OnlinePresentationSession`, which is now the `getMatchedCredentials()` method. `RetrieveCredentialResult` is now a sealed interface with `Success` and `Failure` variants. * **New error cases and discovery fields**: New values were added to `RetrieveCredentialError`, new required authorization-server fields were added to `DiscoveredCredentialOffer`, and `OfferedCredential.claims` is now optional. Update exhaustive `when` statements and any code that constructs these types. * **Session termination change**: The `sessionStatus` parameter was removed from `ProximityPresentationSession.terminateSession`; terminated sessions now send a `SessionTerminated` status to the verifier. *Bug fixes* * Fixed an issue where a malformed Authorization Request JWS would throw an unexpected error. * Added temporal validation of `AuthorizationRequest` `iat`, `exp`, and `nbf` values, which were previously not validated (allowing replay of expired request objects). * Fixed an NFC negotiated-handover issue on certain Samsung devices where device engagement could get stuck in an infinite loop. * Fixed an issue where requesting only an `age_over_xx` attribute during a DCM flow showed an empty consent screen. * Fixed an issue where the OID4VP `state` parameter had no size limit during online presentation. * Fixed an issue where the DCM origin encoded the app signing certificate hash using hex instead of unpadded base64, causing verification to fail. * Fixed a crash during online presentation when the `x5c` header was stripped from a validly-signed request object JWT. * Fixed a crash caused by oversized credential fields during pre-authorized issuance. For a complete description of the changes, see the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/). *End of Life notice* * In line with our SLA, major version `6.x.x` of the Android mDocs Holder SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **22 September 2026**, after which it will no longer be supported. ## SDK Tethering, Wallet Attestation, and breaking changes in the iOS mDocs Holder SDK 2026-06-22 Tags: Holder, iOS This major release (v6.0.0) of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview#ios) introduces SDK Tethering and Wallet attestation, along with a number of breaking API changes, smaller enhancements, and bug fixes. Because this release contains breaking changes, review the [iOS 6.0.0 migration guide](/docs/holding/sdk-operations/migration-guides/ios-6.0.0-migration-guide) before upgrading. *New features* * **SDK Tethering**: The SDK can now register each app instance with your MATTR VII tenant, giving you operational insight into registered and active app instances and establishing a remote management channel we expect to extend in future releases. Tethering is enabled by passing a new optional `platformConfiguration` to `initialize`. It is currently optional but will become required in an upcoming release. See the [SDK Tethering guide](/docs/holding/sdk-operations/sdk-tethering) for details. * **Wallet attestation**: You can now build holder applications that claim credentials from issuers which restrict issuance to trusted wallet applications. When an issuer requires it, the SDK automatically proves the application's authenticity following the OAuth 2.0 Attestation-Based Client Authentication specification. Wallet attestation requires SDK Tethering. See the [Wallet attestation guide](/docs/holding/credential-claiming-guides/wallet-attestation) for details. * **Stronger application identity during issuance**: Pre-authorized issuance flows now pass the application's `client_id`, improving compatibility with issuers applying stricter controls. * **Automatic challenge generation for remote mobile verification**: The `challenge` parameter for remote mobile (app-to-app) verification is now optional. When omitted, the SDK generates a cryptographically secure random challenge automatically. * **Improved biometric error handling**: Signing with a biometric-protected key that has become invalid after re-enrollment now surfaces a clear authentication failure instead of an opaque system error. *Breaking changes* * **Asynchronous initialization**: The `initialize` and `initializeAppExtension` methods are now asynchronous and must be awaited from an asynchronous context. * **New and improved error handling**: New error cases were added to cover license, registration, wallet attestation, and app extension failures, and these must now be handled by your application. * **Renamed and restructured types**: Several types and properties were renamed or restructured for cross-platform consistency, including `MobileCredentialAuthenticationOption` (now `DeviceAuthenticationOption`) and `VerificationResult` (now `MobileCredentialVerificationResult`). `RetrieveCredentialResult` is now an enum with `success` and `failure` cases, and `OnlinePresentationSession.matchedCredentials` is now the `getMatchedCredentials()` method. * **Non-optional properties and stricter validation**: Credential and response collections such as `MobileCredentialResponse.credentials`, `MobileCredential.claims`, and device-key authentication types are now non-optional. Credentials with empty claims are now rejected during retrieval or decoding. A new `.none` case on `UserAuthenticationType` lets you explicitly disable user authentication for a device key. * **OpenID4VCI v1.0 alignment**: The `claims` property on offered credentials is now optional, and new authorization-server discovery properties were added to `DiscoveredCredentialOffer` to support the finalized specification. * **Serialization changes**: The document type now serializes under the `docType` key (previously `doctype`), and verification failure types now serialize as a structured `{type, message}` object. Update any code that persists or transports these values. * **App extension log location change**: App extension logs are now stored in a new location. No code changes are required, but logs written before upgrading will no longer be accessible. *Bug fixes* * Fixed an issue where verifier information was missing from activity log entries recorded during online presentation sessions. * Fixed an issue where a credential-added event was recorded even when every credential in an issuance batch failed. * Fixed an issue where credential offers without claims in issuer metadata were incorrectly rejected during OID4VCI discovery. * Fixed an issue where an unretrievable database encryption key caused a storage initialization error after certain app lifecycle transitions. * Fixed an issue where the proximity presentation session emitted a spurious request-received callback and a redundant termination error on receiving a session status code. * Fixed an issue where credential issuance web authentication was canceled when the app moved to the background, preventing the authorization from completing on return. * Fixed an issue where switching an existing instance to a different app group did not remove the previous app group's data. For a complete description of the changes, see the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog). *End of Life notice* * In line with our SLA, major version `5.x.x` of the iOS mDocs Holder SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **22 September 2026**, after which it will no longer be supported. ## New none option for device key authentication and storage key recovery in the iOS mDocs Holder SDK 2026-06-12 Tags: Holder, iOS This release (v5.2.0) of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview#ios) introduces optional device key authentication and automatic recovery from storage key mismatches. *New features* * **No-authentication device keys**: Credential device keys can now be created with no user authentication requirement, enabled by a new `none` case on `UserAuthenticationType`. Setting `UserAuthenticationType.none` on a `DeviceKeyAuthenticationPolicy` explicitly disables authentication for the key, while a `nil` authentication type continues to inherit the requirement from the SDK initialization configuration as before. *Bug fixes* * The SDK now recovers automatically from a storage key mismatch without data loss. A stored storage key could become stale for a number of reasons, leaving the SDK unable to initialize against existing credential data. This was most commonly observed during background prewarming, where the SDK could launch before the device's first unlock and be unable to read existing keychain items. On initialization, the SDK verifies the active storage key by attempting to decrypt an encrypted on-disk probe. If the recorded key is stale, the SDK enumerates all available keychain candidates, identifies the one that successfully decrypts the probe, updates the stored key, and removes the stale entry. If the probe cannot be read because of filesystem protection, `MobileCredentialHolderError.storageInitializedInBackground` is thrown. See the [SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog) for a complete description of the changes. ## Session correlation with state in the Verifier Web SDK 2026-06-09 Tags: Verifier Web This release (v2.2.0) of the [Verifier Web SDK](/docs/verification/remote-web-verifiers/sdks/overview) introduces the following enhancement: * Added an optional `state` parameter to `requestCredentials()`. Pass an opaque correlation reference, such as an internal record ID, to link a verification session to a record in your own system without maintaining a separate `sessionId` mapping. MATTR VII carries the value through the session and returns it with the result in both same-device and cross-device flows, on both successful and failed results. Refer to [Correlating verification sessions with your system](/docs/verification/remote-web-verifiers/guides/correlating-verification-sessions) for more information. Refer to the [SDK Docs changelog](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/index.html) for more information. ## Bug fix in the React Native mDocs Verifier SDK 2026-06-04 Tags: Verifier Mobile, React Native This release (v9.0.3) of the [React Native mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) includes the following fix: * Fixed an issue on iOS where verification results were not displayed during proximity presentation sessions when BLE mode was set to Peripheral Server and Auto Terminate Session was enabled. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log). ## Bug fix in the iOS mDocs Verifier SDK 2026-06-03 Tags: Verifier Mobile, iOS This release (v5.1.1) of the [iOS mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview#ios) includes the following fix: * Fixed an issue where verification results were not displayed during proximity presentation sessions when BLE mode was set to Peripheral Server and Auto Terminate Session was enabled. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog). ## Bug fix in the Android mDocs Holder SDK 2026-05-25 Tags: Holder, Android This release (v6.1.4) of the [Android mDocs Holder SDK](/docs/holding/sdk-overview#android) includes the following fix: * Fixed an issue where the SDK could crash if DCM configuration cleanup failed. Cleanup errors are now caught and logged, allowing the host app to continue safely. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/). ## Bug fixes in the React Native mDocs Holder SDK 2026-05-18 Tags: Holder, React Native This release (v8.1.3) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview) includes the following iOS platform fixes: * Improved early app launch handling during SDK initialization. The SDK now probes Keychain accessibility before initialization proceeds, ensuring the `storageKeyID` can be loaded reliably before storage is accessed. This enhances protection against prewarming scenarios where Keychain items may be temporarily unavailable, reducing the likelihood of inconsistent storage state or unrecoverable initialization failures. * Resolved an issue that caused a crash when decoding `DiscoveredCredentialOffer` on devices running iOS 17.0 or earlier. * The SDK now supports ES256, ES384, and ES512 signature algorithms, expanding compatibility with a broader range of ECDSA-based cryptographic operations. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html#md:change-log). ## Bug fix in the iOS mDocs Holder SDK 2026-05-14 Tags: Holder, iOS This release (v4.4.1) of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview) includes the following fix: * Improved early iOS app launch handling during SDK initialization. The SDK now probes Keychain accessibility before initialization proceeds, ensuring the `storageKeyID` can be loaded reliably before storage is accessed. This enhances protection against prewarming scenarios where Keychain items may be temporarily unavailable, reducing the likelihood of inconsistent storage state or unrecoverable initialization failures. ## Bug fix in the React Native mDocs Verifier SDK 2026-05-13 Tags: Verifier Mobile, React Native This release (v9.0.2) of the [React Native mDocs Verifier SDK](/docs/verification/sdks/overview) includes the following fix: * Fixed an issue on Android devices where status checks could not be turned off. The SDK now correctly honors the `checkStatus` option, allowing apps to disable status checks on Android devices when required. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log). ## SDK EOL announcement 2026-05-13 Tags: Holder, Verifier Mobile, iOS, Android In line with our SLA, the following SDK versions have now reached their **End of Life (EOL)** and are no longer supported: * iOS mDocs Holder SDK v4. * iOS mDocs Verifier SDK v4. * Android mDocs Holder SDK v5. * Android mDocs Verifier SDK v5. For a list of actively supported versions, please refer to the respective SDK documentation: * [Holder SDKs](/docs/holding/sdk-overview#versions) * [Verifier Mobile SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview#versions) If you have questions about upgrading or need support with migration, please [get in touch](mailto:dev-support@mattr.global). ## Bug fix in the React Native mDocs Holder SDK 2026-05-12 Tags: Holder, React Native This release (v9.0.3) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview) includes the following fix: * Added `None` as a `DeviceKeyAuthenticationType` option. This allows credentials to be generated and/or retrieved with no user authentication requirement, even when the SDK is initialized with `userAuthenticationBehavior` set to `Always` or `OnDeviceKeyAccess`. You can now pass `{ authenticationPolicy: { type: "None" } }` to `retrieveCredentials`, `retrieveCredentialsUsingAuthorizationSession`, or `generateDeviceKey` to opt out of authentication for a specific device key. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html#md:change-log). ## Bug fixes in the React Native mDocs Holder SDK 2026-04-28 Tags: Holder, React Native This release (v9.0.2) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview) includes the following fixes: * General bug fixes: * Fixed an issue where the SDK did not use the OID4VCI nonce endpoint during credential issuance when required by the issuer. The SDK now automatically fetches an issuer-provided nonce from `nonceEndpoint` (when it is advertised in the issuer metadata) and includes it in the device key proof-of-possession JWT, improving compliance with the OID4VCI specification. * Fixed an issue where calling `discoverCredentialOffer` would fail when credential metadata did not include any claims. The `claims` field is now treated as optional and defaults to an empty array when absent. * iOS specific bug fixes: * Fixed an issue where SDK initialization could fail on iOS devices due to inconsistent storage states, such as orphaned keychain keys after an app reinstall or transferred files without keys after device migration. The SDK now performs a preflight check and automatically recovers from these states. * Fixed an issue where SDK storage files and directories could be included in iOS device backups, resulting in encrypted data being backed up without the keys required to restore it. These files are now excluded from iCloud and local backups. * Fixed an issue where calling the `destroy` function did not fully remove persisted data on iOS devices. It now also deletes the database encryption key and storage key ID from the keychain, preventing orphaned keychain entries. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html#md:change-log). ## Bug fixes in the Android mDocs Holder SDK 2026-04-10 Tags: Holder, Android This release (v6.1.2) of the [Android mDocs Holder SDK](/docs/holding/sdk-overview#android) includes the following fixes: * Fixed an issue where the SDK did not use the OID4VCI nonce endpoint during credential issuance when required by the issuer. The SDK now automatically fetches an issuer-provided nonce from `nonceEndpoint` (when it is advertised in the issuer metadata) and includes it in the device key proof-of-possession JWT, improving compliance with the OID4VCI specification. * Fixed an issue where calling `discoverCredentialOffer` would fail when credential metadata did not include the mDoc IACA's URI. The `mdocIacasUri` field is now treated as optional and defaults to an empty string when absent. * Fixed an issue where credentials containing unsupported claims could be stored but could not be retrieved. The SDK now rejects these credentials instead of storing them. * Fixed an issue where CBOR date-time values with nanosecond accuracy could not be parsed. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/). ## Bug fixes in the iOS mDocs Holder SDK 2026-04-10 Tags: Holder, iOS This release (v5.1.1) of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview#ios) includes the following fixes: * Fixed an issue where the SDK did not use the OID4VCI nonce endpoint during credential issuance when required by the issuer. The SDK now automatically fetches an issuer-provided nonce from `nonceEndpoint` (when it is advertised in the issuer metadata) and includes it in the device key proof-of-possession JWT, improving compliance with the OID4VCI specification. * Fixed an issue where calling `discoverCredentialOffer` would fail when credential metadata did not include any claims. The `claims` field is now treated as optional and defaults to an empty array when absent. * Fixed an issue where SDK initialization could fail due to inconsistent storage states, such as orphaned keychain keys after an app reinstall or transferred files without keys after device migration. The SDK now performs a preflight check and automatically recovers from these states. * Fixed an issue where SDK storage files and directories could be included in device backups, resulting in encrypted data being backed up without the keys required to restore it. These files are now excluded from iCloud and local backups. * Fixed an issue where calling the `destroy` function did not fully remove persisted data. It now also deletes the database encryption key and storage key ID from the keychain, preventing orphaned keychain entries. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog). ## Bug fix in the React Native mDocs Holder SDK 2026-03-25 Tags: Holder, React Native This release (v9.0.1) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview) fixes an issue where native binaries were not included in the builds published to NPM. Version 9.0.0 of the React Native mDocs Holder SDK was non-functional and has been removed from NPM. Use v9.0.1 or later. ## Bug fixes in the React Native mDocs Verifier SDK 2026-03-25 Tags: Verifier Mobile, React Native This release (v9.0.1) of the [React Native mDocs Verifier SDK](/docs/verification/sdks/overview) includes the following fixes: * Fixed an issue where native binaries were not included in the builds published to NPM. * Fixed an iOS/Android inconsistency in `requestCredentials` to normalize behavior across platforms. Version 9.0.0 of the React Native mDocs Verifier SDK was non-functional and has been removed from NPM. Use v9.0.1 or later. ## App-to-app verification and Apple Wallet support in the React Native mDocs Verifier SDK 2026-03-23 Tags: Verifier Mobile, React Native We have released a new major version (v9.0.0) of the [React Native mDocs Verifier SDK](/docs/verification/sdks/overview). Version 9.0.0 was non-functional and has been removed from NPM. Use v9.0.1 or later. *Breaking changes* * **Updated initialize signature**: The `initialize` method now accepts an optional options object and returns a `Result` type. All call sites must handle the result and possible errors. * **Simplified status check parameter**: Replaced `skipStatusCheck` with `checkStatus` to improve readability. When set to `true` (default), revocation status is checked as part of credential verification. When set to `false`, revocation status is not checked. * **New proximity session termination error**: Added `Exception` value to `ProximityPresentationSessionTerminationErrorType` as a fallback for unexpected issues during presentation. Exhaustive switches must handle the new case. * **NFC error handling update**: The `registerForNfcDeviceEngagement` method now routes parse failures to `onError` instead of rethrowing. *New features* * **App-to-app verification**: The SDK now supports requesting credentials from another app on the same device using OID4VP, allowing verification flows to be built directly into your apps. * **Verify with Apple Wallet (iOS only)**: The SDK can now request and verify a credential directly from Apple Wallet using the Verify with Wallet API. Only available for iOS 16 and above. * **Logger configuration**: The `initialize` method now supports an optional `loggerConfiguration` parameter to configure the logging level. * **New destroy method**: A new `destroy` method completely resets the SDK state, clearing all persisted storage and permanently deleting all stored certificates. * **Status Lists Draft 14 support**: The SDK now supports the [Token Status List Draft 14](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-14) specification while maintaining existing support for Draft 3. * **Updated COSE algorithm support**: Updated COSE algorithms as per [RFC 9864](https://www.rfc-editor.org/rfc/rfc9864.html) strengthen cryptographic compatibility and ensure continued compliance with evolving standards. This release also includes improved BLE reliability and general stability improvements. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log). *End of Life notice* In line with our SLA, major version `8.x.x` of the React Native mDocs Verifier SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **23 June 2026**, after which it will no longer be supported. ## DC API support, device key authentication and NFC engagement in the React Native mDocs Holder SDK 2026-03-23 Tags: Holder, React Native We have released a new major version (v9.0.0) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview). Version 9.0.0 was non-functional and has been removed from NPM. Use v9.0.1 or later. *Breaking changes* * **OID4VCI v1.0 alignment**: The SDK now implements the finalized OpenID for Verifiable Credential Issuance (OID4VCI) v1.0 specification, upgrading from draft-12. This introduces a new mandatory `credentialConfigurationId` property on `OfferedCredential`. Compatibility with issuers implementing earlier OID4VCI draft versions is no longer guaranteed. * **Simplified status check parameter**: Replaced `skipStatusCheck` with `fetchUpdatedStatusList` to improve readability. When set to `true` (default), the SDK fetches the latest revocation status list from the server. When set to `false`, it uses cached revocation status if valid. * **New initialize error types**: The `initialize` method can now return three new error types: `StorageInitializedInBackground`, `SdkInitialized`, and `InvalidInstanceID`, providing more specific feedback on initialization failures. * **New proximity session termination error**: Added `Exception` value to `ProximityPresentationSessionTerminationErrorType` as a fallback for unexpected issues during presentation. Exhaustive switches must handle the new case. * **Web callback activity path update (Android only)**: The `WebCallbackActivity` class path has moved from `global.mattr.mobilecredential.common.webcallback.WebCallbackActivity` to `global.mattr.mobilecredential.holder.webcallback.WebCallbackActivity`. This only affects Holder apps using web callbacks and requires an Android Manifest update (not a general package import change). *New features* * **Digital Credentials API (DC API) support**: Added support for the DC API, as defined in ISO/IEC 18013-7 Annex C (iOS) and Annex D (Android). This allows wallet apps to register stored credentials with the operating system, enabling them to appear in the DC API's selector UI when a verifier requests credentials. A new optional `dcConfiguration` parameter on `initialize` controls DC API behavior per platform. * **Verifier authentication**: Added support for mDoc Reader Authentication as defined in ISO/IEC 18013-5:2021. The SDK can now inspect the verifier authentication result and enable users to decide whether to share credentials with an unauthenticated verifier. * **Device key authentication**: You can now specify a per-credential authentication policy that defines what user authentication (such as biometrics or device passcode) is required to claim and access a credential. * **NFC proximity presentation (Android only)**: The SDK now supports NFC device engagement on Android, enabling proximity presentation sessions to be started by tapping an NFC-enabled verifier terminal. * **Status Lists Draft 14 support**: The SDK now supports the [Token Status List Draft 14](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-14) specification while maintaining existing support for Draft 3. * **Updated COSE algorithm support**: Updated COSE algorithms as per [RFC 9864](https://www.rfc-editor.org/rfc/rfc9864.html) strengthen cryptographic compatibility and ensure continued compliance with evolving standards. This release also includes general stability and performance improvements. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html#md:change-log). *End of Life notice* In line with our SLA, major version `8.x.x` of the React Native mDocs Holder SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **23 June 2026**, after which it will no longer be supported. ## Activity log bug fix in the Android mDocs Holder SDK 2026-03-12 Tags: Holder, Android This release (v6.1.1) of the Android mDocs Holder SDK fixes an issue where presentation activity events were logged in some cases when a presentation session was not completed successfully (for example, if authentication was canceled). ## Activity logging and status list updates in the Android mDocs Holder SDK 2026-03-06 Tags: Holder, Android Version 6.1.0 of the [Android mDocs Holder SDK](/docs/holding/sdk-overview#android) introduces activity logging and expanded status list support. *New features* * **Activity Log**: Wallet applications can now record and query credential and presentation events, providing a verifiable history of when credentials were added, removed, or presented. Activity logging is configured via `ActivityLogConfiguration` on the `initialize` method and can be enabled or disabled at runtime. * **Token Status List Draft 14 support**: The SDK now supports [Token Status List Draft 14](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-14) alongside the existing Draft 3 support, including updated content types, CWT claim keys, and status list URL formats. The SDK also respects rate limit response headers returned with HTTP 429 responses from status list endpoints, with a configurable default delay for rate-limited requests. *Bug fixes* * Fixed an issue that prevented retrieving credentials without a `Branding.name` value. * Fixed an issue that prevented credential presentation using DCM when `DeviceKeyPolicy` was configured with a timeout of 0. * Fixed an issue where session engagement could hang on Pixel 7 devices. For complete details and technical guidelines, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/). ## Activity logging and status list updates in the iOS mDocs Holder SDK 2026-03-06 Tags: Holder, iOS Version 5.1.0 of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview#ios) introduces activity logging and expanded status list support. *New features* * **Activity Log**: Wallet applications can now record and query credential and presentation events, providing a verifiable history of when credentials were added, removed, or presented. Activity logging is configured via `ActivityLogConfiguration` on the `initialize` method and can be enabled or disabled at runtime. * **Token Status List Draft 14 support**: The SDK now supports [Token Status List Draft 14](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-14) alongside the existing Draft 3 support, including updated content types, CWT claim keys, and status list URL formats. The SDK also respects rate limit response headers returned with HTTP 429 responses from status list endpoints, with a configurable default delay for rate-limited requests. For complete details and technical guidelines, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog). ## Status list updates and teardown support in the Android mDocs Verifier SDK 2026-03-06 Tags: Verifier Mobile, Android Version 6.1.0 of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview#android) adds expanded status list support and a new SDK teardown function. *New features* * **Token Status List Draft 14 support**: The SDK now supports [Token Status List Draft 14](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-14) alongside the existing Draft 3 support, including updated content types, CWT claim keys, and status list URL formats. The SDK also respects rate limit response headers returned with HTTP 429 responses from status list endpoints, with a configurable default delay for rate-limited requests. * **SDK teardown**: A new `MobileCredentialVerifier.destroy()` function allows complete teardown of the SDK instance, clearing all stored data and resetting internal state. This is useful for scenarios such as user logout or account switching. *Bug fixes* * Fixed an issue where session engagement could hang on Pixel 7 devices. For complete details and technical guidelines, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/). ## Status list updates in the iOS mDocs Verifier SDK 2026-03-06 Tags: Verifier Mobile, iOS Version 5.1.0 of the [iOS mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview#ios) adds expanded status list support. *New features* * **Token Status List Draft 14 support**: The SDK now supports [Token Status List Draft 14](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-14) alongside the existing Draft 3 support, including updated content types, CWT claim keys, and status list URL formats. The SDK also respects rate limit response headers returned with HTTP 429 responses from status list endpoints, with a configurable default delay for rate-limited requests. *Bug fixes* * Fixed an issue where HTTP 429 (Too Many Requests) responses did not respect the `Retry-After` header, potentially causing repeated rate-limited requests. For complete details and technical guidelines, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog). ## Bug fix in the React Native mDocs Holder SDK 2026-02-25 Tags: Holder, React Native This release (8.1.2) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview) fixed an issue on Android devices that prevented retrieving credentials when a `Branding.name` value was not set. ## React Native mDocs Verifier SDK maintenance release 2026-02-25 Tags: Verifier Mobile, React Native This release (v8.1.1) of the [React Native mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) is a maintenance release. No new features or breaking changes were introduced. ## Android mDocs Verifier SDK maintenance release 2026-02-23 Tags: Verifier Mobile, Android This release (v5.3.2) of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) is a maintenance release. No new features or breaking changes were introduced. ## Bug fix in the Android mDocs Holder SDK 2026-02-23 Tags: Holder, Android This release (5.3.2) of the [Android mDocs Holder SDK](/docs/holding/sdk-overview) fixed an issue that prevented retrieving credentials when a `Branding.name` value was not set. ## OID4VCI v1.0 support and Digital Credential Manager integration in the Android mDocs Holder SDK 2026-02-13 Tags: Holder, Android We have released a new major version (v6.0.0) of the [Android mDocs Holder SDK](/docs/holding/sdk-overview#android). *Breaking changes* * **OID4VCI v1.0 alignment**: The SDK now implements the finalized OpenID for Verifiable Credential Issuance (OID4VCI) v1.0 specification, upgrading from draft-12. This introduces a new mandatory `credentialConfigurationId` parameter to `OfferedCredential`. Compatibility with issuers implementing earlier OID4VCI draft versions is no longer guaranteed. * **Simplified status check parameter**: Replaced `skipStatusCheck` with `fetchUpdatedStatusList` to improve readability. When set to `true` (default), the SDK fetches the latest revocation status list from the server. When set to `false`, it uses cached revocation status if valid. * **Improved NFC Device Engagement interface**: Updated the NFC Device Engagement API to improve behavior in cases when multiple NFC-capable mDocs holders are installed on the device. * **Verification result always returned**: The `MobileCredential.verificationResult` parameter is no longer optional and is always returned. * **New verifier authentication result**: Introduced `VerifierAuthenticationResult.Unsigned` subtype for cases where the `DeviceRequest` does not include `readerAuth`. The `verifierAuthenticationResult` parameter is now required in `MobileCredentialRequest`. * **Package path update**: The `mattr.global.mobilecredentials.common` package has moved to `mattr.global.mobilecredentials.holder`. *New features* * **Digital Credential Manager (DCM) support**: Added support for Android's Digital Credential Manager, as defined in ISO/IEC 18013-7 Annex D. This allows wallet apps to register stored credentials with the system, enabling them to appear in DCM's selector UI when a verifier requests credentials. * **Improved NFC handling**: Added `MobileCredentialHolder.Nfc.onActivityResume` and `MobileCredentialHolder.Nfc.onActivityPause` methods to better suppress the system NFC disambiguation prompt when your application is ready to handle requests. * **Updated COSE Algorithm Support**: The SDK now aligns with the latest COSE algorithm registrations defined in [RFC 9864](https://www.rfc-editor.org/rfc/rfc9864.html), ensuring compatibility with modern cryptographic standards. This release also includes bug fixes for status list parsing, BLE connection issues on Pixel devices, and NFC engagement reliability. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/). *End of Life notice* In line with our SLA, major version `5.x.x` of the Android mDocs Holder SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **13 May 2026**, after which it will no longer be supported. ## OID4VCI v1.0 support and Digital Credentials API integration in the iOS mDocs Holder SDK 2026-02-13 Tags: Holder, iOS We have released a new major version (v5.0.0) of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview#ios). *Breaking changes* * **OID4VCI v1.0 alignment**: The SDK now implements the finalized OpenID for Verifiable Credential Issuance (OID4VCI) v1.0 specification, upgrading from draft-12. This introduces a new mandatory `credentialConfigurationId` parameter to `OfferedCredential`. Compatibility with issuers implementing earlier OID4VCI draft versions is no longer guaranteed. * **Simplified status check parameter**: Replaced `skipStatusCheck` with `fetchUpdatedStatusList` to improve readability. When set to `true` (default), the SDK fetches the latest revocation status list from the server. When set to `false`, it uses cached revocation status if valid. * **Verification result always returned**: The `MobileCredential.verificationResult` parameter is no longer optional and is always returned. * **Enhanced verifier authentication**: Introduced a new `unsigned` case to `VerifierAuthenticationResult` for scenarios where the `DeviceRequest` does not include `readerAuth`. The `verifierAuthenticationResult` parameter in `MobileCredentialRequest` is now required. * **Synchronous deinitialization**: The `deinitialize` function is now synchronous, resolving concurrency-related crashes. * **New error type**: Added `storageInitializedInBackground` error type to `MobileCredentialHolderError`, thrown when the SDK is initialized in the background. This prevents initialization issues during app prewarming. *New features* * **Digital Credentials API (DC API) support**: Added support for the DC API, as defined in ISO/IEC 18013-7 Annex C. This allows wallet apps to register stored credentials with the operating system, enabling them to appear in DC API's selector UI when a verifier requests credentials. * **Updated COSE Algorithm Support**: The SDK now aligns with the latest COSE algorithm registrations defined in [RFC 9864](https://www.rfc-editor.org/rfc/rfc9864.html), ensuring compatibility with modern cryptographic standards. This release also includes bug fixes for status list parsing with non-standard bit sizes and credential metadata serialization. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog). *End of Life notice* In line with our SLA, major version `4.x.x` of the iOS mDocs Holder SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **13 May 2026**, after which it will no longer be supported. ## Updated COSE algorithm support and simplified integration in the Android mDocs Verifier SDK 2026-02-13 Tags: Verifier Mobile, Android We have released a new major version (v6.0.0) of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview#android). *Breaking changes* * **Simplified status check parameter**: Replaced `skipStatusCheck` with `checkStatus` to improve readability and reduce integration confusion. When set to `true` (default), revocation status is checked as part of credential verification. When set to `false`, revocation status is not checked. * **Package path update**: The `mattr.global.mobilecredentials.common` package has moved to `mattr.global.mobilecredentials.verifier`. *New features* * **Bundled Common dependency**: The `Common` dependency is now bundled into the Verifier SDK and no longer needs to be included separately, simplifying integration. * **Updated COSE Algorithm Support**: The SDK now aligns with the latest COSE algorithm registrations defined in [RFC 9864](https://www.rfc-editor.org/rfc/rfc9864.html), ensuring compatibility with modern cryptographic standards. This release also includes bug fixes for status list parsing, BLE retry handling, and DCM cross-device flow improvements. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/). *End of Life notice* In line with our SLA, major version `5.x.x` of the Android mDocs Verifier SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **13 May 2026**, after which it will no longer be supported. ## Updated COSE algorithm support in the iOS mDocs Verifier SDK 2026-02-13 Tags: Verifier Mobile, iOS We have released a new major version (v5.0.0) of the [iOS mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview#ios). *Breaking changes* * **New error type**: Added `storageInitializedInBackground` error type to `MobileCredentialVerifierError`, thrown when the SDK is initialized in the background. This prevents initialization issues during app prewarming. * **Simplified status check parameter**: Replaced `skipStatusCheck` with `checkStatus` to improve readability. When set to `true` (default), revocation status is checked as part of credential verification. When set to `false`, revocation status is not checked. *New features* * **Updated COSE Algorithm Support**: The SDK now aligns with the latest COSE algorithm registrations defined in [RFC 9864](https://www.rfc-editor.org/rfc/rfc9864.html), ensuring compatibility with modern cryptographic standards. This release also includes bug fixes for status list parsing with non-standard bit sizes and BLE presentation session timeout handling. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog). *End of Life notice* In line with our SLA, major version `4.x.x` of the iOS mDocs Verifier SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **13 May 2026**, after which it will no longer be supported. ## Security update in the Verifier Web SDK 2026-02-04 Tags: Verifier Web This release (v2.1.1) of the Verifier Web SDK updated internal dependencies to address known vulnerabilities and improve overall security stability. ## React Native SDKs EOL announcement 2025-12-12 Tags: Holder, Verifier Mobile, React Native In line with our SLA, the following SDK versions have now reached their **End of Life (EOL)** and are no longer supported: * React Native mDocs Holder SDK v7. * React Native Holder SDK v11. * React Native mDocs Verifier SDK v7. * React Native Verifier SDK v7. For a list of actively supported versions, please refer to the respective SDK documentation: * [Holder SDKs](/docs/holding/sdk-overview#versions) * [Verifier Mobile SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview#versions) If you have questions about upgrading or need support with migration, please [get in touch](mailto:dev-support@mattr.global). ## Enhanced security and cryptographic support in the iOS Holder SDK 2025-12-02 Tags: Holder, iOS This release (v4.4.0) of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview) introduces improved security features and expanded cryptographic algorithm support. *New features* * **HTTPS redirect URI support**: The SDK now supports HTTPS redirect URIs registered through Associated Domains (`webcredentials:`), in addition to existing custom URL scheme redirects. This enables more secure, standards-based authentication flows and simplifies integration for apps that share a verified web domain. This feature is only available on iOS 17.4+. * **Extended ESP algorithm support**: The SDK now also supports the `ESP256`, `ESP384`, and `ESP512` signature algorithms, expanding compatibility with a broader range of ESP-based cryptographic operations. *Bug fixes* * Resolved an issue where early app launch (e.g., during prewarming) could cause `UserDefaults` to be inaccessible, resulting in the `storageKeyID` failing to load and incorrect initialization. The SDK now waits for protected data availability before accessing and persists the `storageKeyID` in the Keychain, ensuring secure and reliable access once protected data becomes available. See the [SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog) for a complete description of the changes. ## Android mDocs Verifier SDK maintenance release 2025-11-27 Tags: Verifier Mobile, Android This release (v5.3.1) of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) is a maintenance release. It includes internal improvements to support future capabilities. ## Bug fix in the Android Holder SDK 2025-11-27 Tags: Holder, Android This release (5.3.1) of the [Android mDocs Holder SDK](/docs/holding/sdk-overview) fixed an issue where cached revocation status was ignored. See the [SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/) for a complete description of the bug and fix. ## Android mDocs Verifier SDK maintenance release 2025-11-21 Tags: Verifier Mobile, Android This release (v5.3.0) of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) is a maintenance release. It includes internal improvements to support future capabilities. ## NFC Device Engagement support in the Android mDocs Holder SDK 2025-11-21 Tags: Holder, Android This release (v5.3.0) of the [Android mDocs Holder SDK](/docs/holding/sdk-overview) introduces support for NFC Device Engagement as specified in [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). Applications using the SDK can now initiate proximity presentation sessions over NFC. See the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/) for a complete description of NFC Device Engagement support and all other changes in this release. ## Bug fix in the iOS mDocs Holder SDK 2025-11-20 Tags: Holder, iOS This release (v4.3.1) of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview) resolved an issue that caused a crash when decoding [`DiscoveredCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/discoveredcredentialoffer) on devices running iOS 17.0 or earlier. ## React Native Holder SDKs EOL announcement 2025-11-19 Tags: Holder, React Native In line with our SLA, the following SDK versions have now reached their **End of Life (EOL)** and are no longer supported: * React Native Holder SDK v10. * React Native mDocs Holder SDK v6. For a list of actively supported versions, please refer to the [Holder SDKs](/docs/holding/sdk-overview#versions) overview page. If you have questions about upgrading or need support with migration, please [get in touch](mailto:dev-support@mattr.global). ## Bug fix in the React Native mDocs Holder SDK 2025-11-18 Tags: Holder, React Native This release (v8.1.1) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview) resolved an issue where calls to [`retrieveCredentialsUsingAuthorizationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/retrieveCredentialsUsingAuthorizationSession.html) would fail on iOS devices running iOS 17.0 or earlier. ## Bug fix in the iOS mDocs Holder SDK 2025-11-13 Tags: Holder, iOS This release (v4.2.1) of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview) resolved an issue that caused a crash when decoding [`DiscoveredCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/discoveredcredentialoffer) on devices running iOS 17.0 or earlier. ## Device key authentication support in the iOS mDocs Holder SDK 2025-11-06 Tags: Holder, iOS We have released a new version (v4.3.0) of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview). *New features* **Device key authentication**: Fine-grained control over credential security is now available through the `DeviceKeyAuthenticationPolicy` parameter. This feature allows you to specify what user authentication methods (such as Face ID, Touch ID, or device passcode) are required when claiming and accessing credentials. By configuring a per-credential authentication policy, you can require stronger methods for sensitive credentials while allowing more convenient access for less sensitive ones. The authentication requirement is bound to the device key protecting the credential, ensuring consistent security throughout the credential's lifecycle. See [Device Key Authentication](/docs/holding/credential-claiming-guides/device-key-authentication-guide) for more information. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog). ## Android mDocs Verifier SDK maintenance release 2025-11-05 Tags: Verifier Mobile, Android This release (v5.2.0) of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) is a maintenance release. It includes internal improvements to support future capabilities. ## Device key authentication support in the Android mDocs Holder SDK 2025-11-05 Tags: Holder, Android We have released a new version (v5.2.0) of the [Android mDocs Holder SDK](/docs/holding/sdk-overview). *New features* **Device key authentication**: Fine-grained control over credential security is now available through the `DeviceKeyAuthenticationPolicy` parameter. This feature allows you to specify what user authentication methods (such as fingerprint authentication, face authentication, or device passcode) are required when claiming and accessing credentials. By configuring a per-credential authentication policy, you can require stronger methods for sensitive credentials while allowing more convenient access for less sensitive ones. The authentication requirement is bound to the device key protecting the credential, ensuring consistent security throughout the credential's lifecycle. See [Device Key Authentication](/docs/holding/credential-claiming-guides/device-key-authentication-guide) for more information. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/). ## mDocs Verifier SDKs maintenance releases 2025-10-23 Tags: Verifier Mobile This release (v8.1.0) of the [React Native mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) is a maintenance release. It includes code refactoring and improvements to support future capabilities. ## OID4VCI manual redirect support and bug fixes in the React Native mDocs Holder SDK 2025-10-23 Tags: Holder, React Native We have released a new version (v8.1.0) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview). *Enhancements* **OID4VCI manual redirect support**: Allows applications to implement the OID4VCI issuance workflow within embedded WebViews while maintaining full control over the redirect flow. Applications can now: 1. Call the new `createAuthorizationSession` method to initiate the flow. This returns an `AuthorizationSession` object containing both the `authorizeUrl` and `codeVerifier` properties. 2. Load the returned `authorizeUrl` in a WebView to handle user authentication and consent. 3. After successful authentication, capture the redirect using the configured `redirectUri`, then extract the authorization code from the returned URL. 4. Complete the issuance workflow by calling the new `retrieveCredentialsUsingAuthorizationSession` method, passing in the `AuthorizationSession` and extracted authorization code. *Bug fixes* * iOS: * Fixed an issue where the passcode fallback button was not displayed when biometric authentication failed and `UserAuthenticationType` was set to `.biometricOrPasscode`. * Fixed an issue where calling `addCredential` with a credential that could not be verified against any stored trusted issuer certificate incorrectly threw `AddMobileCredentialError.certificateNotFound` instead of `AddMobileCredentialError.invalidCredential`. * Android: * Fixed an issue where closing the embedded browser during the OID4VCI authorization flow caused `retrieveCredentials` to hang indefinitely. ## Android mDocs Verifier SDK maintenance release 2025-10-10 Tags: Verifier Mobile, Android This release (v5.1.0) of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) is a maintenance release. It includes code refactoring and improvements to support future capabilities. ## OID4VCI manual redirect support and bug fixes in the Android mDocs Holder SDK 2025-10-10 Tags: Holder, Android We have released a new version (v5.1.0) of the [Android mDocs Holder SDK](/docs/holding/sdk-overview). *Enhancements* **OID4VCI manual redirect support**: Allows applications to implement the OID4VCI issuance workflow within embedded WebViews while maintaining full control over the redirect flow. Applications can now: 1. Call the new [`createAuthorizationSession(credentialOffer:clientId:)`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/create-authorization-session.html) method to initiate the flow. This returns an `AuthorizationSession` object containing both the `authorizeUrl` and `codeVerifier` properties. 2. Load the returned `authorizeUrl` in a WebView to handle user authentication and consent. 3. After successful authentication, capture the redirect using the configured `redirectUri`, then extract the authorization code from the returned URL. 4. Complete the issuance workflow by calling the new `retrieveCredentials(authorizationSession:authorizationCode:)` method, passing in the [`AuthorizationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.issuance.dto/-authorization-session/index.html) and extracted authorization code. *Bug fixes* * Fixed an issue where closing the embedded browser during the OID4VCI authorization flow caused `retrieveCredentials` to hang indefinitely. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/). ## OID4VCI manual redirect support and bug fixes in the iOS mDocs Holder SDK 2025-10-10 Tags: Holder, iOS We have released a new version (v4.2.0) of the [iOS mDocs Holder SDK](/docs/holding/sdk-overview). *Enhancements* **OID4VCI manual redirect support**: Allows applications to implement the OID4VCI issuance workflow within embedded WebViews while maintaining full control over the redirect flow. Applications can now: 1. Call the new `createAuthorizationSession(credentialOffer:clientId:)` method to initiate the flow. This returns an `AuthorizationSession` object containing both the `authorizeUrl` and `codeVerifier` properties. 2. Load the returned `authorizeUrl` in a WebView to handle user authentication and consent. 3. After successful authentication, capture the redirect using the configured `redirectUri`, then extract the authorization code from the returned URL. 4. Complete the issuance workflow by calling the new `retrieveCredentials(authorizationSession:authorizationCode:)` method, passing in the `AuthorizationSession` and extracted authorization code. *Bug fixes* * Fixed an issue where the passcode fallback button was not displayed when biometric authentication failed and `UserAuthenticationType` was set to `.biometricOrPasscode`. * Fixed an issue where calling `retrieveCredentials` before initializing the SDK did not correctly throw the expected `MobileCredentialHolderError.sdkNotInitialized` error. * Fixed an issue where calling `addCredential` with a credential that could not be verified against any stored trusted issuer certificate incorrectly threw `MobileCredentialHolderError.certificateNotFound` instead of `MobileCredentialHolderError.invalidCredential`. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog). ## SDK EOL announcement 2025-10-07 Tags: Holder, Verifier Mobile, iOS, Android In line with our SLA, the following SDK versions have now reached their **End of Life (EOL)** and are no longer supported: * iOS mDocs Holder SDK v3. * iOS mDocs Verifier SDK v3. * Android mDocs Holder SDK v3. * Android mDocs Verifier SDK v3. For a list of actively supported versions, please refer to the respective SDK documentation: * [Holder SDKs](/docs/holding/sdk-overview#versions) * [Verifier Mobile SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview#versions) If you have questions about upgrading or need support with migration, please [get in touch](mailto:dev-support@mattr.global). ## Dependency upgrades and build fixes in the React Native Holder SDK 2025-10-06 Tags: Holder, React Native We have released a new version (v12.1.0) of the [React Native Holder SDK](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/12.1.0/index.html). *Enhancements* * Upgraded SDK dependencies to remediate potential vulnerabilities. *Bug fixes* * Resolved build failures in applications using the SDK when the React Native New Architecture was enabled. ## App-to-App verification support in the Android mDocs Verifier SDK 2025-09-30 Tags: Verifier Mobile, Android We have released a new major version (v5.0.0) of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview#android). *New features* * **Remote app verification support**: Introduced the `requestMobileCredentials` method to support App-to-App verification flows. * **Enhanced error visibility**: Detailed IACA validation errors are now surfaced during credential verification, including specific errors for certificate expiry, validity periods, and trust chain failures. *Breaking changes* * `TrustedIssuerCertificateNotFound` error is now returned when a matching trusted issuer certificate is not found. * The `MobileCredentialVerifier.initialize` method now accepts an additional optional `platformConfiguration` parameter for MATTR VII tenant URL configuration. This release also includes performance improvements for storage operations with large data sets and bug fixes for NFC Device Engagement with Apple Wallet. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/). *End of Life notice* * In line with our SLA, major version `4.x.x` of the Android mDocs Verifier SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **30 December 2025**, after which it will no longer be supported. ## Authentication enhancements in the Android mDocs Holder SDK 2025-09-30 Tags: Holder, Android We have released a new major version (v5.0.0) of the [Android mDocs Holder SDK](/docs/holding/sdk-overview#android). *New features* * **Customizable authentication prompts**: Authentication prompt title and description are now exposed as overrideable string resources (`@string/global_mattr_unlock_storage_title` and `@string/global_mattr_unlock_storage_description`). *Breaking changes* * JSON representation of `VerifierAuthenticationResult` now uses `result` instead of `type` for the authentication result type. *Bug fixes* * Fixed issue where special characters in credential offers were escaped twice. * Fixed incorrect exception handling in `OnlinePresentationSession.sendResponse` method. * Resolved issue where `MobileCredentialHolder.addCredential` could throw incorrect exceptions during trust chain evaluation failures. This release also includes performance improvements for storage operations with large data sets. For complete details, refer to the [full SDK changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/). *End of Life notice* * In line with our SLA, major version `4.x.x` of the Android mDocs Holder SDK has now entered its **Maintenance** phase and will reach its **End of Life (EOL)** on **30 December 2025**, after which it will no longer be supported. ## Verifier Web SDK maintenance release 2025-09-16 Tags: Verifier Web This release of the [Verifier Web SDK](/docs/verification/remote-web-verifiers/sdks/overview) (v2.1.0) is a maintenance release. It includes code refactoring and improvements to support future capabilities. ## New React Native architecture support and spelling standardization in the React Native mDocs Holder SDK 2025-09-12 Tags: Holder, React Native We have released a new version (v8.0.0) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview). *Breaking changes* * **React Native compatibility**: Added support for React Native’s [New Architecture](https://reactnative.dev/architecture/landing-page). * The minimum supported React Native version is now `0.78.0`. * **Spelling standardization (UK → US English)**: The SDK’s naming convention has been updated to use US English. This affects several methods and error types: * Methods: * `initialise` → `initialize` * `deinitialise` → `deinitialize` * Errors: * `MobileCredentialHolderError.AuthenticationCancelled` →\ `MobileCredentialHolderError.AuthenticationCanceled` * `MobileCredentialHolderError.InvalidAuthorisationRequestUri` →\ `MobileCredentialHolderError.InvalidAuthorizationRequestUri` * `MobileCredentialHolderError.InvalidAuthorisationRequestVerifiedByCertificate` →\ `MobileCredentialHolderError.InvalidAuthorizationRequestVerifiedByCertificate` * `MobileCredentialHolderError.InvalidAuthorisationRequestVerifiedByDomain` →\ `MobileCredentialHolderError.InvalidAuthorizationRequestVerifiedByDomain` * **Error handling consolidation**: The `getCredential` method now returns a new verification failure reason: * `MobileCredentialVerificationFailureType.TrustedIssuerCertificateNotFound`,\ used when a credential cannot be verified due to a missing matched trusted issuer certificate. *Enhancements* * A new `msoHash` property has been added to the `MobileCredential` and `MobileCredentialMetadata` types. This property represents a hashed Mobile Security Object, as defined in [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html).\ It is distinct from the `id` property and should not be used in the `getCredential` method. * **iOS platform improvements**: The SDK now enforces checks for empty CBOR arrays.\ Previously, credentials with an empty `IssuerNamespaces` field were accepted, but under the CDDL specification in ISO/IEC 18013-5, this field must include at least one entry.\ This update ensures stricter standards compliance and improves interoperability. *End of Life notice* * In line with our SLA, version `7.0.0` of the React Native mDocs Holder SDK has now entered its **Maintenance** phase and will reach **End of Life (EOL)** on **12 December 2025**, after which it will no longer be supported. ## New React Native architecture support and spelling standardization in the React Native mDocs Verifier SDK 2025-09-12 Tags: Verifier Mobile, React Native We have released a new version (v8.0.0) of the [React Native mDocs Verifier SDK](/docs/verification/sdks/overview). *Breaking changes* * **React Native compatibility**: Added support for React Native’s [New Architecture](https://reactnative.dev/architecture/landing-page). * The minimum supported React Native version is now `0.78.0`. * **Spelling standardization (UK → US English)**: The SDK’s naming convention has been updated to use US English. This affects the following methods: * `initialise` → `initialize` * `deinitialise` → `deinitialize` * **Error handling consolidation**: Updates have been made to error handling in key methods: * `createProximityPresentationSession`: * The `onError` callback may now be invoked with: * `MobileCredentialVerifierErrorType.FailedToCreateProximityPresentationSession` when a session cannot be created. * `MobileCredentialVerifierErrorType.UnknownError` when an unexpected exception occurs. * The function may also return: * `MobileCredentialVerifierErrorType.IllegalState` when receiving an unexpected function call (for example, if no presentation session is established). * `sendProximityPresentationRequest`: * A new verification failure reason `MobileCredentialVerificationFailureType.TrustedIssuerCertificateNotFound` is now returned when a presented credential cannot be verified due to a missing matched trusted issuer certificate. *Enhancements* * **iOS platform improvements**: The SDK now enforces checks for empty CBOR arrays.\ Previously, credentials with an empty `IssuerNamespaces` field were accepted, but under the CDDL specification in [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html), this field must include at least one entry.\ This update ensures stricter standards compliance and improves interoperability. *End of Life notice* * In line with our SLA, version `7.0.0` of the React Native mDocs Verifier SDK has now entered its **Maintenance** phase and will reach **End of Life (EOL)** on **12 December 2025**, after which it will no longer be supported. ## New React Native architecture support, spelling standardization and new features in the React Native Holder SDK 2025-09-12 Tags: Holder, React Native We have released a new version (v12.0.0) of the [React Native Holder SDK](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/latest/index.html). *Breaking changes* * **React Native compatibility**: Added support for React Native’s [New Architecture](https://reactnative.dev/architecture/landing-page). * The minimum supported React Native version is now `0.78.0`. * To use the New Architecture, update the @mattrglobal/pairing-crypto-rn peer dependency to version `0.4.4`. * **Spelling standardization (UK → US English)**: The SDK’s naming convention has been updated to use US English. This affects methods, types, and error definitions: * Methods: * `initialise` → `initialize` * `AuthenticationCancelled` → `AuthenticationCanceled` * Types: * `InitialiseOptions` → `InitializeOptions` * `AuthenticationCancelledError` → `AuthenticationCanceledError` * Errors (mDocs): * `MobileCredentialHolderError.AuthenticationCancelled` →\ `MobileCredentialHolderError.AuthenticationCanceled` * `MobileCredentialHolderError.InvalidAuthorisationRequestUri` →\ `MobileCredentialHolderError.InvalidAuthorizationRequestUri` * `MobileCredentialHolderError.InvalidAuthorisationRequestVerifiedByCertificate` →\ `MobileCredentialHolderError.InvalidAuthorizationRequestVerifiedByCertificate` * `MobileCredentialHolderError.InvalidAuthorisationRequestVerifiedByDomain` →\ `MobileCredentialHolderError.InvalidAuthorizationRequestVerifiedByDomain` *Features* * **New property for credentials**: Added an `msoHash` property to the `MobileCredential` and `MobileCredentialMetadata` types.\ This property represents a hashed Mobile Security Object, as defined in [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html).\ It is distinct from the `id` property and should not be used in the `credential.mobile.getCredential` method. * **iOS platform improvements**: The SDK now enforces checks for empty CBOR arrays.\ Previously, credentials with an empty `IssuerNamespaces` field were accepted, but under the CDDL specification in ISO/IEC 18013-5, this field must include at least one entry.\ This update ensures stricter standards compliance and improves interoperability. *End of Life notice* * In line with our SLA, version `11.0.0` of the React Native mDocs Holder SDK has now entered its **Maintenance** phase and will reach **End of Life (EOL)** on **12 December 2025**, after which it will no longer be supported. ## React Native compatibility and error handling changes in the React Native Verifier SDK 2025-09-12 Tags: Verifier Mobile, React Native We have released a new version (v8.0.0) of the [React Native Verifier SDK](https://api-reference-sdk.mattr.global/verifier-sdk-react-native/latest/index.html). *Breaking changes* * **React Native compatibility**: The minimum supported React Native version is now `0.78.0`. * React Native’s [New Architecture](https://reactnative.dev/architecture/landing-page) is not yet supported. * **Spelling standardization (UK → US English)**: The SDK’s naming convention has been updated to use US English. This affected the following error type: * Renamed `MobileCredentialVerifierErrorType.SdkNotInitialised` to `MobileCredentialVerifierErrorType.SdkNotInitialized`. * **Error handling consolidation**: Updates have been made to error handling in key methods: * `mobile.createProximityPresentationSession`: * The `onError` callback may now be invoked with: * `MobileCredentialVerifierErrorType.FailedToCreateProximityPresentationSession` when a session cannot be created. * `MobileCredentialVerifierErrorType.UnknownError` when an unexpected exception occurs. * `mobile.sendProximityPresentationRequest`: * A new verification failure reason `MobileCredentialVerificationFailureType.TrustedIssuerCertificateNotFound` is now returned when a presented credential cannot be verified due to a missing matched trusted issuer certificate. *Enhancements* * **iOS platform improvements**: The SDK now enforces checks for empty CBOR arrays.\ Previously, credentials with an empty `IssuerNamespaces` field were accepted, but under the CDDL specification in [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html), this field must include at least one entry. This update ensures stricter standards compliance and improves interoperability. *End of Life notice* * In line with our SLA, version `7.0.0` of the React Native Verifier SDK has now entered its **Maintenance** phase and will reach **End of Life (EOL)** on **12 December 2025**, after which it will no longer be supported. ## iOS mDocs Holder 4.1.1 released 2025-09-03 Tags: Holder, iOS This release fixed a rare crash that could occur when the [initialize](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/initialize\(instanceid:userauthenticationconfiguration:credentialissuanceconfiguration:loggerconfiguration:\)) and [deinitialize](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/deinitialize\(\)) methods were called concurrently. ## Verifier Web SDK 1.1.0 EOL announcement 2025-08-29 Tags: Verifier Web In line with our SLA, the Verifier Web SDK v1.1.0 has now reached its **End of Life (EOL)** and is no longer supported. For a list of actively supported versions, please refer to the [Verifier Web SDK documentation](/docs/verification/remote-web-verifiers/sdks/overview#versions). If you have questions about upgrading or need support with migration, please [get in touch](mailto:dev-support@mattr.global). ## SDK EOL announcement 2025-08-26 Tags: Holder, Verifier Mobile, iOS, Android In line with our SLA, the following SDK versions have now reached their **End of Life (EOL)**: and are no longer supported: * iOS mDocs Holder SDK v2.0.0. * iOS mDocs Verifier SDK v2.0.0. * Android mDocs Holder SDK v2.0.0. * Android mDocs Verifier SDK v2.0.0. * React Native mDocs Verifier SDK v6.0.0. For a list of actively supported versions, please refer to the respective SDK documentation: * [Holder SDKs](/docs/holding/sdk-overview#versions) * [Verifier Mobile SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview#versions) If you have questions about upgrading or need support with migration, please [get in touch](mailto:dev-support@mattr.global). ## Improved certificate management and iOS data protection in the React Native Holder SDK 2025-08-19 Tags: Holder, React Native We have released a new version (v11.0.0) of the React Native Holder SDK. *Breaking changes* * The SDK no longer supports credentials with the `BbsBlsSignature2020` proof type. Please ensure issued credentials use supported alternatives. *mDocs Enhancements* * The SDK no longer checks the signature algorithm when adding verifier certificates. This aligns with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html), which does not specify required algorithms for reader root certificates. * RSA is now supported as a digital signature algorithm for Reader Authentication root certificates. * On iOS, the SDK’s storage data protection class has been updated from Class C (Protected Until First User Authentication) to Class B (Protected Unless Open), improving data availability while maintaining platform security guarantees.\ See [Apple Platform Security](https://support.apple.com/en-nz/guide/security/secb010e978a/web) for more on protection classes. *Bug fixes* * Fixed an issue where special characters in credential offers were escaped twice. * Resolved a potential crash when establishing a BLE connection. * Fixed an issue where the browser would retain focus after authenticating during credential retrieval via OID4VCI. * Fixed validation for ES384 and ES512 signatures. *Other updates* * Removed the `elliptic` dependency, previously used for signature format conversion. This functionality is now handled by an internal implementation. * Updated the peer dependency `react-native-cryptography` to version `2.1.2`. *End of Life notice* * In line with our SLA, version 10.0.0 of the React Native Holder SDK has now entered its **Maintenance** phase and will reach **End of Life (EOL)** on **19 November 2025**, after which it will no longer be supported. ## Improved iOS data protection and signature validation in the React Native Verifier SDK 2025-08-19 Tags: Verifier Mobile, React Native We have released a new version (v7.2.0) of the React Native Verifier SDK. *mDocs Enhancements* * On iOS, the SDK’s storage data protection class has been updated from Class C (Protected Until First User Authentication) to Class B (Protected Unless Open), improving data availability while maintaining platform security guarantees.\ See [Apple Platform Security](https://support.apple.com/en-nz/guide/security/secb010e978a/web) for more on protection classes. *Bug fixes* * Resolved a potential crash when establishing a BLE connection. * Fixed validation for ES384 and ES512 signatures. *Other updates* * Updated the peer dependency `react-native-cryptography` to version `2.1.2`. ## New lifecycle management and improved iOS data protection in the React Native mDocs Verifier SDK 2025-08-19 Tags: Verifier Mobile, React Native We have released a new version (v7.2.0) of the [React Native mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview). *Enhancements* * Introduced a new `isInitialised` method to check whether the SDK is currently initialized. * Added a `deinitialise` method to explicitly terminate the current presentation session and close access to SDK storage. * On iOS, the SDK’s storage data protection class has been updated from Class C (Protected Until First User Authentication) to Class B (Protected Unless Open), improving data availability while maintaining platform security guarantees.\ See [Apple Platform Security](https://support.apple.com/en-nz/guide/security/secb010e978a/web) for more on protection classes. *Bug fixes* * Resolved a potential crash when establishing a BLE connection. * Fixed validation for ES384 and ES512 signatures. ## New Pre-Authorized Code flow support and improved error handling in the React Native mDocs Holder SDK 2025-08-19 Tags: Holder, React Native We have released a new version (v7.0.0) of the [React Native mDocs Holder SDK](/docs/holding/sdk-overview). *Breaking changes* * The SDK now supports credential claiming using the [OID4VCI Pre-Authorized Code Flow](/docs/issuance/pre-authorized-code/overview), enabling a more seamless credential retrieval experience. This change introduces updates to several method signatures and response structures: * `retrieveCredentials` now accepts the original offer URL as a `String`, an optional `transactionCode`, and no longer includes `autoTrustMobileIaca` or `redirectUri`, which are now set in the `initialise` method. * The following fields are now handled internally and no longer exposed from `discoverCredentialOffer`: * `authorizeEndpoint` * `tokenEndpoint` * `credentialEndpoint` * `mdocIacasUri`. * A new `TransactionCode` struct is returned by `discoverCredentialOffer`. * The `initialise` method now supports user authentication configuration through a new `UserAuthenticationConfiguration` object, replacing the previous `userAuthRequiredOnInitialise` parameter. This allows developers to specify both **when** and **how** authentication is required, including biometric-only or passcode options on iOS. * Several error types have been consolidated to simplify handling: * Errors from `discoverCredentialOffer` like `CredentialOfferNotFound` and `IssuerMetadataServiceError` are now grouped under `DiscoverCredentialOfferErrorType.FailedToDiscoverCredentialOffer`. * Errors from `retrieveCredentials` like `AuthCodeNotFound` and `DeviceKeyGenerationError` are replaced with broader categories such as `WebAuthenticationFailed` and `FailedToDiscoverCredentialOffer`. *Enhancements* * Future-dated credentials are now supported via the `addCredential` method. * The SDK no longer checks the signature algorithm when adding verifier certificates, aligning with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * On iOS, the SDK’s storage data protection class has been updated from Class C (Protected Until First User Authentication) to Class B (Protected Unless Open), improving data availability while maintaining platform security guarantees.\ See [Apple Platform Security](https://support.apple.com/en-nz/guide/security/secb010e978a/web) for more on protection classes. * RSA is now supported as a digital signature algorithm for Reader Authentication root certificates. *Bug fixes* * Fixed an issue where special characters in credential offers were escaped twice. * Resolved a potential crash when establishing a BLE connection. * Fixed an issue where the browser would retain focus after authenticating during credential retrieval via OID4VCI. * Fixed validation for ES384 and ES512 signatures. *End of Life notice* * In line with our SLA, version 6.0.0 of the React Native mDocs Holder SDK has now entered its **Maintenance** phase and will reach **End of Life (EOL)** on **19 November 2025**, after which it will no longer be supported. ## Bug fix in the Android Holder SDK 2025-08-12 Tags: Holder, Android We have released new versions of the following Android SDKs: *Android mDocs Holder SDK v4.1.1* * Fixed an issue where special characters in credential offers were escaped twice. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/) for full details. *Android mDocs Verifier SDK v4.1.1* * This release contains no functional changes, new features, or bug fixes beyond ensuring interoperability with version 4.1.1 of the Android mDocs Holder SDK. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) for full details. ## IACA validation detailed errors surfacing, enhanced spec compliance and improved concurrency safety in the native SDKs 2025-08-12 Tags: Holder, Verifier Mobile, iOS, Android We have released new versions of the following native SDKs with more detailed IACA validation errors surfacing, enhanced spec compliance and improved concurrency safety: *Android mDocs Holder SDK v5.0.0-rc.1* * The authentication prompt's title and description are now exposed as `@string/global_mattr_unlock_storage_title` and `@string/global_mattr_unlock_storage_description`. Developers can customize these strings in their app's resource files. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/) for full details. *Android mDocs Verifier SDK v5.0.0-rc.1* * When validating a credential's chain of certificates as part of credential verification, the SDK now surfaces detailed errors to help identify the root causes of IACA validation failures. This makes previously hidden issues more visible and enables developers to more effectively troubleshoot integration issues and build clearer error feedback into user experiences. New surfaced errors include: * `TrustedIssuerCertificateNotFound`: When no matching trusted issuer certificate is found. * `TrustedIssuerCertificateExpired`: When the matching trusted issuer certificate has expired. * `TrustedIssuerCertificateNotYetValid`: When the matching trusted issuer certificate is not yet valid. * Fixed an issue with NFC device engagement with Apple Wallet running on iOS 26. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) for full details. *iOS mDocs Holder SDK v4.2.0-rc.1* * The SDK now enforces that the `IssuerNamespaces` field in credentials must contain at least one entry, as required by the Concise Data Definition Language (CDDL) specification in ISO/IEC 18013-5. Previously, credentials with an empty `IssuerNamespaces` field were allowed. This update improves standards compliance and interoperability by preventing empty CBOR arrays in this field. * The SDK’s public data types now conform to `Sendable`, improving concurrency safety and ensuring compatibility with Swift 6’s stricter requirements. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog) for full details. *iOS mDocs Verifier SDK v5.0.0-rc.1* * When validating a credential's chain of certificates as part of credential verification, the SDK now surfaces detailed errors to help identify the root causes of IACA validation failures. This makes previously hidden issues more visible and enables developers to more effectively troubleshoot integration issues and build clearer error feedback into user experiences. New surfaced errors include: * `TrustedIssuerCertificateNotFound`: When no matching trusted issuer certificate is found. * `TrustedIssuerCertificateExpired`: When the matching trusted issuer certificate has expired. * `TrustedIssuerCertificateNotYetValid`: When the matching trusted issuer certificate is not yet valid. * The SDK now enforces that the `IssuerNamespaces` field in credentials must contain at least one entry, as required by the Concise Data Definition Language (CDDL) specification in ISO/IEC 18013-5. Previously, credentials with an empty `IssuerNamespaces` field were allowed. This update improves standards compliance and interoperability by preventing empty CBOR arrays in this field. * The SDK’s public data types now conform to `Sendable`, improving concurrency safety and ensuring compatibility with Swift 6’s stricter requirements. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog) for full details. ## Refined verifier certificates validation in the iOS and Android mDocs Verifier SDKs 2025-07-29 Tags: Verifier Mobile, iOS, Android We have released new versions of the following native SDKs with refined verifier certificates validation and miscellaneous bug fixes: *Android mDocs Holder SDK v4.1.0* * The SDK no longer checks the signature algorithm when adding verifier certificates. This aligns with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html), which does not specify required algorithms for reader root certificates. * Miscellaneous bug fixes and improvements: * Resolved a potential crash when establishing a BLE connection. * Fixed an issue where the browser would retain focus after authenticating during credential retrieval via OID4VCI. * Fixed validation for ES384 and ES512 signatures. * Enabled using RSA as a digital signature algorithm for Reader Authentication root certificates. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/) for full details. *Android mDocs Verifier SDK v4.1.0* * Miscellaneous bug fixes and improvements: * Resolved a potential crash when establishing a BLE connection. * Fixed validation for ES384 and ES512 signatures. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) for full details. *iOS mDocs Holder SDK v4.1.0* * The SDK no longer checks the signature algorithm when adding verifier certificates. This aligns with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html), which does not specify required algorithms for reader root certificates. * Enabled using RSA as a digital signature algorithm for Reader Authentication root certificates. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog) for full details. *iOS mDocs Verifier SDK v4.1.0* * Resolved a potential crash when establishing a BLE connection. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog) for full details. ## Enhanced support for latest React Native versions in the React Native Mobile Credential Verifier SDK 2025-07-14 Tags: Verifier Mobile, React Native We have released new versions of the following React Native SDKs: *React Native mDocs Verifier SDK v7.1.0* * Added support for React Native versions 0.77, 0.78, and 0.79. * Resolved an issue where the incorrect callback was triggered when a proximity session was terminated. Developers should now use the `onTerminated` callback instead of `onSessionTerminated` for accurate session handling. * Resolved issues related to Bluetooth permissions handling and session termination. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log) for a complete description of the changes. *React Native Verifier SDK v7.1.0* * Added support for React Native versions 0.77, 0.78, and 0.79. * Resolved an issue where the incorrect callback was triggered when a proximity session was terminated. Developers should now use the `onTerminated` callback instead of `onSessionTerminated` for accurate session handling. * Resolved issues related to Bluetooth permissions handling and session termination. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/verifier-sdk-react-native/latest/index.html#md:change-log) for a complete description of the changes. ## New authentication options and mDoc Reader authentication in native SDKs 2025-07-07 Tags: Holder, Verifier Mobile, iOS, Android We have released new versions of the following native SDKs with enhanced authentication controls and support for mDoc Reader authentication: *Android mDocs Holder SDK v4.0.0* * Configure when biometric authentication is required, allowing more flexible user flows. * Support for mDoc Reader authentication as defined in [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html), enabling holders to choose whether to share credentials if the verifier cannot be authenticated. * Ability to claim credentials with future effective dates. * The Holder SDK now exposes the Mobile Security Object (MSO) hash in the `getCredential` and `getCredentials` API responses. This immutable identifier simplifies record matching between the Holder SDK and MATTR VII. * The `deinitialize` method is now idempotent and non-throwing. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/) for full details. *Android mDocs Verifier SDK v4.0.0* * The `deinitialize` method is now idempotent and non-throwing. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) for full details. *iOS mDocs Holder SDK v4.0.0* * Configure when biometric authentication is required for greater control over user interactions. * Support for mDoc Reader authentication as defined in [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Ability to claim credentials with future effective dates. * The Holder SDK now exposes the Mobile Security Object (MSO) hash in the `getCredential` and `getCredentials` API responses. This immutable identifier simplifies record matching between the Holder SDK and MATTR VII. * Storage data protection class updated from C (Protected Until First User Authentication) to B (Protected Unless Open). See [Apple Platform Security forum](https://support.apple.com/en-nz/guide/security/secb010e978a/web) for more information. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog) for full details. *iOS mDocs Verifier SDK v4.0.0* * The SDK can now request and verify a credential directly from an Apple Wallet using the [Verify with Wallet API](https://developer.apple.com/wallet/get-started-with-verify-with-wallet/). Only available for iOS 16 and above. * New `initialized` read-only property to indicate SDK initialization state. * New `deinitialize` method to terminate sessions and close SDK storage. * Storage data protection class updated from C to B. See [Apple Platform Security forum](https://support.apple.com/en-nz/guide/security/secb010e978a/web) for more information. * See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog) for full details. ## Standalone React Native mDocs Holder SDK now available 2025-05-29 Tags: Holder, React Native The new version (v6.0.0) of the React Native mDocs Holder SDK is now available as a standalone SDK. This focused, lightweight evolution of our React Native Holder SDK offers a streamlined experience purpose-built for handling mDocs. Refer to the following resources for more information: * [SDK Overview](/docs/holding/sdk-overview). * SDK quickstart guides: * [Claim a credential](/docs/holding/sdk-quickstart). * [Remote presentation](/docs/holding/sdk-quickstart). * [Proximity presentation](/docs/holding/sdk-quickstart). * SDK tutorials: * [Claim a credential](/docs/holding/credential-claiming-tutorial). * [Remote presentation](/docs/holding/remote-presentation-tutorial). * [Proximity presentation](/docs/holding/proximity-presentation-tutorial). * [Reference SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html). ## Support for aborting a session in the Verifier Web SDK 2025-05-29 Tags: Verifier Web This release of the Verifier Web SDK (v2.0.2) introduces the following enhancements: * The SDK now includes an `abortCredentialRequest` method for aborting the currently active session. * In same-device flows, the browser window now closes automatically when the verification process continues on a separate redirect page. * When a response includes invalid credentials, the SDK no longer returns a `PresentationFailureResult` error. Instead, it returns a `PresentationSuccessResult` that includes details about any failed credentials and the reasons for their failure. * The SDK now uses US English spelling conventions instead of UK English. See the [SDK Docs change log](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/index.html#md:200) for a full list of related changes. * Consolidation of parameters passed to the `requestCredentials` method. Refer to the [SDK Docs changelog](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/index.html#md:200) for more information. ## Pre-authorized Code support, Mobile app verification and NFC in-person verification now available in native SDKs 2025-05-26 Tags: Holder, Verifier Mobile, iOS, Android We have released new versions of the following native SDKs: *Android mDocs Holder SDK v3.0.0* * The SDK now supports claiming credentials via the [OID4VCI Pre-authorized Code flow](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-pre-authorized-code-flow). * The SDK now uses US English spelling conventions instead of UK English. See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/) for a full list of related changes. * Error handling consolidation. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/) for a complete description of new capabilities and breaking changes. *Android mDocs Verifier SDK v3.0.0* * The SDK now supports NFC device engagement. * Simplification of proximity presentation state handling. * The SDK now uses US English spelling conventions instead of UK English. See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) for a full list of related changes. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) for a complete description of new capabilities and breaking changes. *iOS mDocs Holder SDK v3.0.0* * The SDK now supports claiming credentials via the [OID4VCI Pre-authorized Code flow](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-pre-authorized-code-flow). * The SDK now uses US English spelling conventions instead of UK English. See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog) for a full list of related changes. * The SDK now supports the mac device authentication method. * Error handling consolidation. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog) for a complete description of new capabilities and breaking changes. *iOS mDocs Verifier SDK v3.0.0* * The SDK now supports [mobile app verification](/docs/verification/remote-mobile-verifiers/workflow), allowing your verifier application to request and verify an mDoc from another application installed on the same iOS device. * Simplification of proximity presentation state handling. * The SDK now uses US English spelling conventions instead of UK English. See the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog) for a full list of related changes. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog) ## React Native Verifier SDKs release 2025-05-26 Tags: Verifier Mobile, React Native We have released new versions of the following React Native SDKs: *React Native mDocs Verifier SDK v7.0.0* * Simplified handling of proximity presentation sessions. * Support for NFC device engagement (Android devices only). * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log) for a complete description of new capabilities and breaking changes. *React Native Verifier SDK v7.0.0* * Simplified handling of mDocs proximity presentation sessions. * Support for NFC device engagement (Android devices only) for mDocs proximity verification sessions. * The SDK can no longer be used to verify JSON credentials using the BBS 2020 proof type (i.e. `BbsBlsSignature2020`). * Removed the `elliptic` dependency, which was previously used for signature format conversion. This functionality is now handled by an internal implementation. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/verifier-sdk-react-native/latest/index.html#md:change-log) for a complete description of new capabilities and breaking changes. ## Improved React Native SDKs compatibility with older iOS versions 2025-02-05 Tags: Holder, Verifier Mobile, React Native We have released new versions of the following React Native SDKs: *React Native Holder SDK v10.0.0* * Applications embedding the SDK can now run on iOS 13 without crashing, as long as the SDK is not initialized (the SDK features still require iOS 15 or later). * Removed `statusInfo` from mobile credential presentation responses. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/latest/index.html#md:change-log) for a complete description of new capabilities and breaking changes. *React Native Verifier SDK v6.0.0* * Applications embedding the SDK can now run on iOS 13 without crashing, as long as the SDK is not initialized (the SDK features still require iOS 15 or later). * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/verifier-sdk-react-native/latest/index.html#md:change-log) for a complete description of new capabilities and breaking changes. *React Native mDocs Verifier SDK v6.0.0* * Applications embedding the SDK can now run on iOS 13 without crashing, as long as the SDK is not initialized (the SDK features still require iOS 15 or later). * Removed `statusInfo` from mobile credential presentation responses. * [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log) for a complete description of new capabilities and breaking changes. ## Native SDKs release 2025-02-05 Tags: Holder, Verifier Mobile, iOS, Android We have released new versions of the following native SDKs: *Android mDocs Holder SDK v2.0.0* * The `statusInfo` property is no longer returned as part of verification results. * Authorization requests for online presentation workflows must now include the `client_id` as a subject alternative name record. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/) for a complete description of new capabilities and breaking changes. *Android mDocs Verifier SDK v2.0.0* * The `statusInfo` property is no longer returned as part of verification results. * The `getTrustedIssuerCertificates` function now computes the `TrustedCertificate.verificationResult` as well. * Improved performance of `addTrustedIssuerCertificates` when adding certificates with revocation lists. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) for a complete description of new capabilities and breaking changes. *iOS mDocs Holder SDK v2.0.0* * The minimum deployment target is now set to iOS 13. This enables clients to support users up to iOS 13, as long as the SDK is not initialized (The SDK functionality is only available for devices from iOS 15 onwards). * The `statusInfo` property is no longer returned as part of verification results. * Authorization requests for online presentation workflows must now include the `client_id` as a subject alternative name record. * Refined error messages in the `createProximityPresentationSession` API. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog) for a complete description of new capabilities and breaking changes. *iOS mDocs Verifier SDK v2.0.0* * The minimum deployment target is now set to iOS 13. This enables clients to support users up to iOS 13, as long as the SDK is not initialized (The SDK functionality is only available for devices from iOS 15 onwards). * The `statusInfo` property is no longer returned as part of verification results. * The `getTrustedIssuerCertificates` function now computes the `TrustedCertificate.verificationResult` as well. * Refined error messages in the `createProximityPresentationSession` API. * Downloading status lists is now rate limited at 10 requests per second. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog) ## React Native SDKs now support mDocs revocation as well as using holder and verifier SDKs in a single application 2025-02-05 Tags: Holder, Verifier Mobile, React Native We have released new versions of the following React Native SDKs: *React Native Holder SDK v9.0.0* * The SDK now supports checking the [revocation](/docs/issuance/revocation/overview) status of claimed mDocs. * The SDK can now be used in the same application with compatible versions of the [React Native mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview), as detailed in the [getting started](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/latest/index.html#md:install-dependencies) section. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/latest/index.html#md:change-log) for a complete description of new capabilities and breaking changes. *React Native Verifier SDK v5.0.0* * The SDK now supports checking the [revocation](/docs/issuance/revocation/overview) status of presented mDocs. * The SDK can now be used in the same application with compatible versions of the React Native Holder SDK, as detailed in the [getting started](https://api-reference-sdk.mattr.global/verifier-sdk-react-native/latest/index.html#md:install-dependencies) section. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/verifier-sdk-react-native/latest/index.html#md:change-log) for a complete description of new capabilities and breaking changes. *React Native mDocs Verifier SDK v5.0.0* * The SDK now supports checking the [revocation](/docs/issuance/revocation/overview) status of presented mDocs. * The SDK can now be used in the same application with compatible versions of the React Native Holder SDK, as detailed in the [getting started](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:install-dependencies) section. * Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log) for a complete description of new capabilities and breaking changes. ## Android mDocs SDKs now support using mDocs Holder and Verifier SDKs in a single application 2025-01-13 Tags: Holder, Verifier Mobile, Android We have released new versions of the following Android SDKs: *Android mDocs Holder SDK v1.1.0* * The SDK can now be used in the same application with version 1.1.0 of the [Android mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview). *Android mDocs Verifier SDK v1.1.0* * The SDK can now be used in the same application with version 1.1.0 of the [Android mDocs Holder SDK](/docs/holding/sdk-overview). ## Android SDKs EOL announcement 2025-01-12 Tags: Holder, Verifier Mobile, Android In line with our SLA, the following SDK versions have now reached their **End of Life (EOL)** and are no longer supported: * Android mDocs Holder SDK v4. * Android mDocs Verifier SDK v4. For a list of actively supported versions, please refer to the respective SDK documentation: * [Holder SDKs](/docs/holding/sdk-overview#versions) * [Verifier Mobile SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview#versions) If you have questions about upgrading or need support with migration, please [get in touch](mailto:dev-support@mattr.global). ## Support for multiple verifier applications in the Verifier Web SDK 2025-01-08 Tags: Verifier Web This release (v1.1.0) of the Verifier Web SDK introduces support for multiple verifier applications interacting with a single MATTR VII tenant to perform [online verification](/docs/verification/remote-web-verifiers/workflow) of [mDocs](/docs/concepts/mdocs). To support this feature, the SDK can now be initialized with an `applicationId` to indicate to the MATTR VII tenant what application the request is coming from. Refer to the [SDK Docs changelog](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/index.html#md:110) for more information. ## Revocation and online presentation now available in first GA release of native mDocs Holder and Verifier SDKs 2024-12-02 Tags: Holder, Verifier Mobile, iOS, Android We have released the first General Availability (GA) version of our iOS and Android mDocs Holder and Verifier SDKs. Built around the most recent standards and best practices, these SDKs offer tools to assist developers integrating credential holding, presentation and verification capabilities into their native applications. *Holder SDKs key features* Both the iOS (v1.0.1) and Android (v1.0.1) mDocs Holder SDKs support a similar set of capabilities: * **Claim an mDoc** * Interact with a credential offer and claim an mDoc as per [OID4VCI (OpenID for Verifiable Credential Issuance)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html). * Manage a list of trusted issuers which mDoc offers can be validated against. * Store claimed mDocs and manage access to that storage. * Manage access to device keys which are bound to issued mDocs. * Use referenced Status lists to check mDocs' [revocation status](/docs/issuance/revocation/overview). * **Present an mDoc** * Present a claimed mDoc for verification via a [proximity presentation workflow](/docs/verification/in-person-overview) as per [ISO 18013-5](https://www.iso.org/standard/69084.html). * Present a claimed mDoc for verification via a [remote (online) presentation workflow](/docs/verification/remote-overview) as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). Refer to the following pages for more information: * iOS Holder SDK: * [Overview](/docs/holding/sdk-overview) * [Claim a credential tutorial](/docs/holding/credential-claiming-tutorial) * [Proximity presentation tutorial](/docs/holding/proximity-presentation-tutorial) * [SDK Reference Docs](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk) * Android Holder SDK: * [Overview](/docs/holding/sdk-overview) * [SDK Reference Docs](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/) *Verifier SDKs key features* Both the iOS (v1.0.1) and Android (v1.0.0) mDocs Verifier SDKs support a similar set of capabilities: * **Verify an mDoc**: * Request and verify mDocs via a proximity presentation workflow as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Manage a list of trusted issuer certificates which presented mDocs can be validated against. * Manage a list of mDocs status lists which is used to check the [revocation status](/docs/issuance/revocation/overview). Refer to the following pages for more information: * iOS Verifier SDK: * [Overview](/docs/verification/remote-mobile-verifiers/sdks/overview) * [Proximity verification tutorial](/docs/holding/proximity-presentation-tutorial) * [SDK Reference Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk) * Android Verifier SDK: * [Overview](/docs/verification/remote-mobile-verifiers/sdks/overview) * [SDK Reference Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) To get started with any of these SDKs, please [contact us](mailto:sales@mattr.global) so we can work together to find the best solution for you. ## New versions of React Native SDKs 2024-11-15 Tags: Holder, Verifier Mobile, React Native We have released new versions of the following SDKs: *React Native Holder SDK* This new major version (v8.0.0) introduces the following changes: * Introduced support for online presentation of mDocs via OID4VP as per ISO/IEC 18013-7:2025. * Replacing Realm database with a custom storage system for all mDocs data. * Various enhancements and refinements to existing SDK methods. * Minimum supported React Native version is now v0.73. Refer to the [SDK Docs changelog](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/latest/index.html#md:change-log) for a detailed description of the changes. *React Native Verifier SDK* This new major version (v4.0.0) introduces the following changes: * Replacing Realm database with a custom storage system for all mDocs data. * Various enhancements and refinements to existing SDK methods. * Minimum supported React Native version is now v0.73. Refer to the [SDK Docs changelog](https://api-reference-sdk.mattr.global/verifier-sdk-react-native/latest/index.html#md:change-log) for a detailed description of the changes. *React Native mDocs Verifier SDK* This new major version (v4.0.0) introduces the following changes: * Replacing Realm database with a custom storage system for all mDocs data. * Various enhancements and refinements to existing SDK methods. * Minimum supported React Native version is now v0.73. Refer to the [SDK Docs changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log) for a detailed description of the changes. ## Terminology updates across MATTR platforms 2024-10-15 Tags: Holder, Verifier Mobile, Verifier Web, iOS, Android, React Native To make it easier to consume our capabilities, we have made some changes to our terminology to describe credentials supported by MATTR platforms by their underlying technology and standards. The following credential formats are supported: * [mDocs](/docs/concepts/mdocs): Previously referred to as Mobile credentials. * [CBOR Web Tokens (CWT) credentials](/docs/concepts/cwt): Previously referred to as Compact credentials. * JSON credentials: Previously referred to as Web credentials. ## New Verifier Web SDK enables online verification of Mobile Credentials 2024-10-01 Tags: Verifier Web Our new [Verifier Web SDK](/docs/verification/remote-web-verifiers/sdks/overview) (v1.0.0) is now available, integrating with an existing MATTR VII tenant to easily add online verification capabilities into your web applications. Using this SDK your web applications can now securely verify mDocs presented via both same-device and cross-device flows, as per [ISO/IEC 18013-7](https://www.iso.org/standard/91154.html) and [OpenID for Verifiable Presentations (OID4VP)](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). ### Features [#features] * Compliant with [ISO/IEC 18013-7](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). * Supports both same-device and cross-device flows. * Simple integration into existing web applications. ### Additional information [#additional-information] * Refer to the [Verifier Web SDK Docs](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/index.html) for a detailed description of this new SDK. * Refer to the Docs section to learn more about the [online verification flow](/docs/verification/remote-overview) and its [implementation](/docs/verification/remote-web-verifiers/workflow). ## Refined error handling and extended features in the React Native Mobile Credential Verifier SDK 2024-09-17 Tags: Verifier Mobile, React Native A new major version (v3.0.0) of the React Native Mobile Credential Verifier SDK is now available, offering refined error handling, extended features and several bug fixes. ### Breaking changes [#breaking-changes] * The `createProximityPresentationSession` method now returns the `MobileCredentialVerifierErrorType.BluetoothDisabled` error when Bluetooth is powered off. * The `sendProximityPresentationRequest` method now returns the `MobileCredentialVerifierErrorType.SessionTerminated` error when the current proximity presentation session is terminated. * The `onSessionTerminated` callback function for `createProximityPresentationSession` method can now be invoked with the `ProximityPresentationSessionTerminationError.ResponseNotReceived` error when a Mobile Credential response was not received from holder. ### Features [#features] The SDK now supports: * Additional extended key usage in a document signer certificate. * Clock skew tolerance during verification. * React Native 0.72.14. ### Bug Fixes [#bug-fixes] * To mitigate a potential race condition issue, we have improved the process of generating new encryption keys. #### iOS specific [#ios-specific] * Fixed an issue where Bluetooth sessions might have been terminated before data exchange was completed. This could have occurred when the developer terminates the session after receiving a single response, but that response doesn't include all the required data. This might happen on devices with lower data transmission speed or when the response size is too big. #### Android specific [#android-specific] * Fixed an issue where the SDK was unable to handle Mobile Credential presentation responses with floating point claims. ## Support for React Native 0.72.14 and local secure store in the React Native Verifier SDK 2024-09-17 Tags: Verifier Mobile, React Native A new minor version (v3.1.0) of the React Native Verifier SDK is now available, introducing support for React Native 0.72.14 as well as local secure store implementation. ### Features [#features] The SDK now supports: * React Native 0.72.14. * Local secure store implementation. Peer dependency `react-native-secure-key-store` is no longer required, but you must include `@mattrglobal/react-native-cryptography@2.0.0` in your application dependencies. ## Improved encryption key generation in the React Native Wallet SDK 2024-08-09 Tags: Holder, React Native The new version (v7.0.1) of our React Native Wallet SDK is now available, improving the process of generating new encryption keys to mitigate a potential race condition issue. Note that when consuming this new version of the SDK, you must upgrade your application dependencies to include `@mattrglobal/react-native-cryptography@2.0.0`. Refer to the [MATTR Pi React Native Wallet SDK Docs](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/latest/index.html#md:change-log) for a detailed description of the new features and changes included in this release. ## Enhanced security in the MATTR Pi Wallet SDK 2024-06-20 Tags: Holder, React Native The latest version (v7.0.0) of our MATTR Pi Wallet SDK is out, providing default configurations that enhance security around JSON-LD contexts. ### Breaking Changes [#breaking-changes] * Unknown JSON-LD contexts are now invalid by default. * In-line JSON-LD contexts definitions are now disabled by default. When using the default configuration, you must now manually whitelist any referenced JSON-LD contexts, and remove any in-line JSON-LD context definitions. ### Key Features [#key-features] * Introducing support for React Native (RN) 0.72.0. * Miscellaneous bug fixes. Refer to the [MATTR Pi Wallet SDK Docs](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/latest/index.html#md:change-log) for a detailed description of the new features and changes included in this release. ## New capabilities in the MATTR Pi Verifier SDK 2024-04-05 Tags: Verifier Mobile, React Native The latest version (v3.0.0) of our MATTR Pi Verifier SDK is out, making more capabilities available for developers to build into their applications. #### Key Features [#key-features] * Introducing [Mobile Credentials](/docs/concepts/mdocs) capabilities via an extension: * Manage a list of trusted issuer certificates which presented Mobile Credentials can be validated against. * Interface with a Mobile Credential holder to request presentations of issued Mobile Credentials as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Verify incoming Mobile Credentials presentations. * Introducing [Ecosystem](/docs/digital-trust-service) capabilities via an extension: * Confirm the verified credential is valid in the ecosystem. * Confirm the verified credential was issued by a valid ecosystem issuer. #### Breaking changes [#breaking-changes] * Two initialization parameters have been renamed to maintain consistency across credential profiles (without affecting their functionality): * The `assertNotBefore` parameter has been renamed to `assertValidFrom`. * The `assertExpiry` parameter has been renamed to `assertValidUntil`. Refer to the [MATTR Pi Verifier SDK Docs](https://api-reference-sdk.mattr.global/verifier-sdk-react-native/latest/index.html) for a detailed description of the new features and changes included in this release. ## New functionalities and improved error handling in the MATTR Pi Wallet SDK 2024-01-31 Tags: Holder, React Native We are releasing a new version (v5.0.0) of our MATTR Pi Wallet SDK. #### Key features [#key-features] * Introducing Android support for [Mobile Credentials](/docs/concepts/mdocs) capabilities via the @mattrglobal/mobile-credential-holder-react-native extension integration. Supported on both iOS and Android. * Introducing support for [Ecosystem](/docs/digital-trust-service) capabilities via the @mattrglobal/ecosystem-sdk-react-native extension integration. * Remote DID document and context resolution is now cached to prevent unnecessary requests and improve overall performance. * Bug fixes and overall performance enhancements. Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/latest/index.html#md:change-log) for more details. #### Breaking changes [#breaking-changes] This release introduces several changes to error handling that should be handled as breaking changes. Refer to the [SDK Docs change log](https://api-reference-sdk.mattr.global/wallet-sdk-react-native/latest/index.html#md:change-log) for a complete description of the new errors and how they might affect your implementation. ## Enhancements and new features across MATTR platforms 2023-07-07 Tags: Holder, React Native We're excited to announce the following updates and new features across our MATTR platforms: * You can now issue Compact Credentials using the OpenID Credential Provisioning flow. This includes using all the features that this flow enables, such as integration hooks, claims source integration and multi-credential issuance for Compact Credentials. * We've enabled the MATTR Pi Wallet Toolkit with the capability to retrieve and hold Compact Credentials. * We now support multiple key types within a single DID, which means you can issue credentials in multiple Credential Profiles using the same DID. ## Introducing MATTR Pi: Our flexible tools for creating apps your users will love 2023-02-22 Tags: Holder, Verifier Mobile, React Native We are excited to announce the official launch of MATTR Pi - our SDK-centric platform encompassing toolkits with the flexibility to build solutions that work for you. Whilst pi (π) is a mathematical constant, its decimals are infinite. All MATTR Pi toolkits have our rigorous commitment to international standards and leading security practices baked in, but they equip you with the limitless potential to create value in this new world of digital trust. The foundations of MATTR Pi, available now, are the MATTR Pi Wallet Toolkit and the MATTR Pi Compact Credentials Verifier Toolkit. * The Wallet Toolkit includes all you need to get started creating your own digital wallet experiences quickly. * The Compact Credentials Verifier Toolkit lets you easily integrate verification capabilities into any existing or new application. Interested in creating a verifier or wallet solution and not sure where to start? [Get in touch](mailto:dev-support@mattr.global) with us today to discuss the best option for your business. ## Compact Credential Verifier SDK 2022-07-07 Tags: Verifier Web We’ve transformed our Credential Verification capabilities to support the wider platform by making it work on your own mobile experience or integrating them into other types of your applications. Utilizing our SDK will significantly reduce your development time while ensuring you are leveraging safe and reliable code libraries. The following capabilities & benefits are provided in the Verifier SDK: * Build your own Compact Credential verification solution into existing applications using the same tools as the MATTR Verifier App. * Validate Compact and Semantic Compact Credentials' authenticity. * Offline verification of your trusted credentials. * Refresh cached revocation lists and trusted issuers. * Privacy-preserving features. * Ongoing support for fast-evolving standards of digital trust and verifiable data. * Create both iOS and Android apps using the same codebase. Interested in learning more about how you might use the MATTR Verifier SDK? [Get in touch](mailto:dev-support@mattr.global) with us today. # MATTR VII Changelog URL: /docs/resources/changelog/vii [Subscribe](/docs/resources/changelog/newsletter-signup) to our release newsletter to get the latest updates on product releases, new features and bug fixes. ## Supported versions [#supported-versions] Below are supported versions of the MATTR VII Platform, including the current active version, supported versions, expected end-of-life (EOL) dates and reference documentation. | Major version | Status | Minor version | End of Life date | Documentation | | ------------- | ------ | ------------- | ---------------- | ------------------------------------------------------------------------------------------------- | | v12 | Latest | 12.19.0 | - | [MATTR Platform 12.19.0 API Reference](/docs/api-reference) | | v12 | Active | 12.18.0 | - | [MATTR Platform 12.18.0 API Reference](https://release-platform-12-18-0.learn.mattrarchives.com/) | | v12 | Active | 12.17.0 | - | [MATTR Platform 12.17.0 API Reference](https://release-platform-12-17-0.learn.mattrarchives.com/) | ## MATTR VII Management API maintenance release 2026-07-01 Tags: Maintenance, Platform Management This release of the MATTR VII Management API (v1.33.2) is a maintenance release. It includes internal changes to enhance performance and stability. ## RICAL and 3-tier PKI support in MATTR VII 2026-07-01 Tags: Verification, Trust Networks This release of the MATTR VII Platform (v12.19.0) introduces the following changes: * **RICAL support**: MATTR VII now supports RICALs (Reader Identity Certificate Authority Lists), the counterpart to a VICAL for verifier trust. Where a VICAL distributes trusted mDoc issuer roots, a RICAL collects and validates Reader Root Certificates from relying parties and signs them into a single list that holder applications (wallets) can consume. When a wallet trusts a RICAL, it can authenticate any verifier whose certificate chain anchors to a root included in the list, without maintaining a direct trust relationship with each individual verifier. To learn more, refer to the [RICAL overview](/docs/digital-trust-service/rical-overview), the [RICAL guide](/docs/digital-trust-service/rical-guide), and the [RICAL consumption](/docs/digital-trust-service/rical-consumption) page. * **3-tier PKI model support**: The Digital Trust Service now supports a 3-tier certificate model in addition to the existing 2-tier model when signing trusted lists. The 3-tier model introduces a DTS intermediate CA certificate between the DTS root CA and the signer certificate (VICAL Signer Certificate or RICAL Signer Certificate), letting you keep the DTS root CA private key offline and delegate day-to-day signing to the intermediate CA. The 3-tier model is supported for unmanaged (external) DTS certificates. To learn more, refer to the [DTS certificates overview](/docs/digital-trust-service/certificates-overview). * **Link certificate support**: MATTR VII now supports maintaining trust continuity for DTS participant issuer and verifier certificates via link certificates. When creating an issuer or verifier certificate, you can add it as a successor to a previously uploaded certificate by providing the predecessor's identifier and a link certificate that ties the two together. This preserves trust for relying parties and wallets across a certificate rotation without them first having to consume an updated VICAL or RICAL. To learn more, refer to linked certificates in the [VICAL overview](/docs/digital-trust-service/vical-overview#linked-certificates) and the [RICAL overview](/docs/digital-trust-service/rical-overview#linked-certificates). ## RICAL trust lists and externally managed DTS certificate chains in the MATTR Portal 2026-07-01 Tags: Platform Management, Trust Networks This release of the MATTR Portal (v1.79.1) introduces the following enhancements: * **RICAL trust list support**: Extends MATTR DTS capabilities by adding support for generating and publishing RICAL (Reader Identity Certificate Authority List) trust lists. To learn more, refer to the [RICAL overview](/docs/digital-trust-service/rical-overview) and the [RICAL guide](/docs/digital-trust-service/rical-guide). * **Externally managed DTS certificate chains**: Introduces the capability to configure an externally managed DTS certificate chain with optional intermediate certificates. To learn more, refer to the [DTS certificates overview](/docs/digital-trust-service/certificates-overview). * **Trust continuity via link certificates**: Adds support for maintaining DTS participant issuer and verifier trust continuity via link certificates. When adding an issuer or verifier certificate, you can now link it to a previously uploaded certificate as a successor. To learn more, refer to linked certificates in the [VICAL overview](/docs/digital-trust-service/vical-overview#linked-certificates) and the [RICAL overview](/docs/digital-trust-service/rical-overview#linked-certificates). ## Holder and Verifier SDK Tethering support in MATTR VII 2026-06-22 Tags: Platform Management, Holding, Verification This release of the MATTR VII Platform (v12.18.0) introduces the following changes: * **Holder and Verifier SDK Tethering**: MATTR VII now supports SDK Tethering for both the MATTR Holder SDKs and the MATTR Verifier SDKs. Tethering registers each app instance with your MATTR VII tenant, establishing a trusted relationship between the SDK and the platform that underpins capabilities such as Wallet Attestation. Refer to the [SDK Tethering](/docs/holding/sdk-operations/sdk-tethering) guide and the [Holder SDK overview](/docs/holding/sdk-overview) for Holder SDK details, and the [Verifier SDK overview](/docs/verification/remote-mobile-verifiers/sdks/overview) for the Verifier SDKs. * **Deprecation of the authenticated Status list retrieval endpoints**: The following authenticated endpoints are now deprecated. In line with our SLA, they will be removed in a release at least 90 days from this announcement: * `GET /v2/credentials/mobile/status-lists` (Retrieve all Status lists) * `GET /v2/credentials/mobile/status-lists/{statusListId}` (Retrieve a Status list) These endpoints are superseded by the existing public Status list endpoints. To discover the Status lists available on a tenant, use the [Status list distribution](/docs/api-reference/platform/status-list-retrieval/getStatusListDistribution) endpoint (`GET /v2/credentials/mobile/status-lists/distribution`). To retrieve an individual Status list token, use the [Retrieve a Status list token](/docs/api-reference/platform/status-list-retrieval/getStatusListToken) endpoint (`GET /v2/credentials/mobile/status-lists/{statusListId}/token`). We recommend migrating any integrations away from the deprecated endpoints before the end of the deprecation window. ## MATTR VII Management API maintenance release 2026-06-22 Tags: Maintenance, Platform Management This release of the MATTR VII Management API (v1.33.1) is a maintenance release. It includes internal changes to enhance performance and stability. ## Global logout and consistent certificate pages in the MATTR Portal 2026-06-08 Tags: Platform Management This release of the MATTR Portal (v1.78.1) introduces the following enhancements: * **Global logout support**: Logging out now ends all of a user's Portal sessions across every device, rather than only the current session, and revokes any tokens issued to that user (note that client credentials are not affected by logouts). * **Consistent certificate pages**: The certificate upload preview and certificate details pages have been updated across all certificate types for a more consistent user experience. ## Global logout support in MATTR VII Management API 2026-06-08 Tags: Platform Management This release of the MATTR VII Management API (v1.33.0) introduces the following enhancement: * **Global logout support**: Logging out from the Portal now ends all of a user's Portal sessions across every device, rather than only the current session, and revokes any issued tokens. This is enabled by a new endpoint offered by the Management API. ## Localized claim labels, larger pre-authorized offers, and user deletion changes in MATTR VII 2026-06-08 Tags: Issuance, Verification This release of the MATTR VII Platform (v12.17.0) introduces the following enhancements: * **Localized claim labels**: Issuers can now define optional translations of their claim names in their credential configurations. These localized labels are published to the issuer metadata at the `/.well-known/openid-credential-issuer` endpoint, so wallet apps can discover them and render claim names in the holder's preferred language. Localized labels are currently supported for mDoc credential configurations. Refer to [Localized display labels](/docs/issuance/credential-configuration/overview#localized-display-labels) and the [credential configuration guide](/docs/issuance/credential-configuration/guide#credential-content) for more information. * **Larger Pre-authorized Code offer payloads**: The maximum payload size for Pre-authorized Code credential offers has been increased from 100KB to 500KB. Refer to [How to create an OID4VCI credential offer](/docs/issuance/credential-offer/guide#generate-an-offer-uri) for best practices on payload size when delivering credentials over BLE compared with remote presentation. * **User deletion now invalidates issued credentials**: When a user is deleted, all of their data is removed and any credential previously issued to them is no longer valid. Refer to [Users](/docs/issuance/users/overview) for more information. * **Session correlation with `state`**: Verifier web applications can now pass an optional opaque `state` value to `requestCredentials()` and receive it back in the result, letting them attach their own correlation reference to a presentation session without implementing separate state management. MATTR VII threads the value through the OpenID4VP presentation flow and returns it on both successful and failed results, in both same-device and cross-device flows. Refer to [Correlating verification sessions with your system](/docs/verification/remote-web-verifiers/guides/correlating-verification-sessions) for more information. ## Participants and Trust Lists split, simplified VICAL creation, and DC API testing in the MATTR Portal 2026-05-26 Tags: Platform Management, Trust Networks, Verification This release of the MATTR Portal (v1.77.0) introduces the following enhancements: * **Participants and Trust Lists pages**: To simplify the interface and prepare for upcoming digital trust service features, the previous Ecosystem page has been split into two dedicated sections under *Digital Trust Service*: **Participants** (for managing issuer participants and their IACA certificates) and **Trust Lists** (which hosts the **VICAL (Trusted issuers)** tab where you configure and publish VICALs). The Ecosystem name/ID box and edit/delete modals have been removed from these pages. The **Ecosystem** page is now only available for the initial one-time ecosystem creation. Once an ecosystem exists, the page is no longer shown and all subsequent management happens through the Participants and Trust Lists pages. * **Simplified VICAL creation**: Removed the Format field and consolidated all VICAL configuration into a single collapsible card. * **DC API support in the web verifier Test modal**: The Test modal for web verifier applications now supports the [Digital Credentials API](/docs/verification/remote-web-verifiers/dc-api/overview) (DC API) as well. ## MATTR VII Platform maintenance release 2026-05-25 Tags: Maintenance This release of the MATTR VII Platform API (v12.16.0) is a maintenance release. It includes internal changes to support upcoming features. ## Token revocation in MATTR VII Management API 2026-05-25 Tags: Platform Management This release of the MATTR VII Management API (v1.32.0) introduces the following enhancement: * **Token revocation for Management API**: Administrators can now immediately revoke a user's access to the Management API, ensuring that compromised or offboarded users lose access without waiting for their tokens to expire. Access can be restored at any time, and revocation does not prevent the user from signing in again if they remain authorized. ## OpenID4VP V1 support for DC API web verification in MATTR VII 2026-05-12 Tags: Verification This release of the MATTR VII Platform (v12.15.0) introduces the following enhancements: * **OpenID4VP V1.0 alignment for DC API**: The tech preview implementation of the [Digital Credentials API](https://wicg.github.io/digital-credentials/) (DC API) for web verification is now aligned with [OpenID4VP V1.0](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). This update ensures that credential presentation requests via the DC API conform to the latest OpenID for Verifiable Presentations specification. * **Expanded claims source mapping parameters**: Claims source configurations can now map from a new `wallet` object (exposing `wallet.id` and `wallet.instanceId`) and from a new `issuanceProtocol` parameter that identifies the protocol used to issue the credential (currently `openid4vci`). The existing `client` object (`client.instanceId`) remains available for backwards compatibility but is being phased out. Update existing configurations to map from `wallet.instanceId` instead. Refer to the [claims source overview](/docs/issuance/claims-source/overview) for more information. ## MATTR VII Management API maintenance release 2026-05-11 Tags: Maintenance, Platform Management This release of the MATTR VII Management API (v1.31.0) is a maintenance release. It includes internal changes to enhance performance and stability. ## Compressed access tokens and certificate details in the MATTR Portal 2026-04-29 Tags: Platform Management This release of the MATTR Portal (v1.76.0) introduces the following enhancements: * **Compressed access token support**: The Portal was upgraded to handle compressed access tokens used by the [Management API v1.30.0](#token-compression-and-authorization-cache-enhancements-in-mattr-vii-management-api-2026-04-28). * **Certificate detail section**: Added a Certificate detail section to all certificate types, which displays certificate details in a human-readable format. ## MATTR VII Platform maintenance release 2026-04-28 Tags: Maintenance This release of the MATTR VII Platform API (v12.14.0) is a maintenance release. It includes internal changes to support upcoming features. ## Token compression and authorization cache enhancements in MATTR VII Management API 2026-04-28 Tags: Platform Management This release of the MATTR VII Management API (v1.30.0) introduces the following enhancements: * **Access token compression**: Permissions are now compressed when issuing MATTR VII access tokens. This prevents exceeding the HTTP request header payload size limit (16KB), which would otherwise result in an HTTP 431 status code (Request Header Fields Too Large). * **Authorization cache reliability**: Enhanced reliability of the internal authorization module cache. ## DC API verification and claim sources scope in MATTR Portal 2026-04-16 Tags: Platform Management, Issuance, Verification This release of the MATTR Portal (v1.75.0) introduces the following enhancements: * **DC API Verifier Applications**: You can now use the MATTR Portal to create Android and Web Verifier Applications that use the Digital Credentials API (DC API) to request and verify credentials remotely. * **Claim sources scope configuration**: You can now add a `scope` field to the OAuth client credentials authorization configuration for claim sources. This supports integration with data servers that require the resource identifier to be passed as a `scope` rather than an `audience` parameter. Refer to the [claims source API reference](/docs/issuance/claims-source/api-reference#configure-a-claims-source) for more information. ## Claim sources OAuth scope configuration in MATTR VII 2026-04-13 Tags: Issuance This release of the MATTR VII platform (v12.13.0) introduces the following enhancement: * **OAuth scope parameter for claim sources**: The OAuth client credentials authorization configuration for claim sources now supports an optional `scope` field. This enables integration with data servers that require the resource identifier to be passed as a `scope` rather than an `audience` parameter. Existing claim sources are unaffected by this change. Refer to the [claims source API reference](/docs/issuance/claims-source/api-reference#configure-a-claims-source) for more information on how to use this new parameter. ## Maintenance update in MATTR VII Management API 2026-04-13 Tags: Maintenance, Platform Management This release of the MATTR VII Management API (v1.29.3) is a maintenance release. It includes internal changes to enhance performance and stability. ## Credential reports in the MATTR Portal 2026-04-01 Tags: Platform Management, Issuance This release of the MATTR Portal (v1.74.0) introduces the following enhancements: * **Credential reports**: You can now generate reports of credential lifecycle operations across your tenant, broken down by day, credential profile, and credential type. Reports cover issuance events and status changes (such as revocations) for CWT and mDoc credentials. Refer to [Credential reports](/docs/issuance/credential-reports/guide) for more information. * **UX enhancements**: Miscellaneous UX and display improvements. ## Credential reports in MATTR VII 2026-03-30 Tags: Issuance This release of the MATTR VII platform (v12.12.0) introduces the following enhancements and fixes: * **Credential reports**: You can now generate reports of credential lifecycle operations across your tenant, broken down by day, credential profile, and credential type. Reports cover issuance events and status changes (such as revocations) for CWT and mDoc credentials. Refer to [Credential reports](/docs/issuance/credential-reports/guide) for more information. * **Bug fixes**: Miscellaneous bug fixes and stability improvements. ## Maintenance update in MATTR VII Management API 2026-03-30 Tags: Maintenance, Platform Management This release of the MATTR VII Management API (v1.29.1) is a maintenance release. It includes internal changes to enhance performance and stability. ## Analytics event purging configuration and Status List specification update in MATTR VII 2026-03-16 Tags: Platform Management, Issuance This release of the MATTR VII platform (v12.11.0) introduces the following enhancements: * **Configurable analytics event purging**: The schedule in which Platform Events that are older than the set retention period are purged can now be configured per tenant. [Contact us](mailto:dev-support@mattr.global) if you want to change this setting for your tenant. * **Support for Draft 14 of the Status List specification**: MATTR VII now supports Draft 14 of the Status List specification for mDocs revocation. This update introduces several key changes to improve alignment with the latest standards: | Aspect | Legacy Format | Latest Specification | | -------------------------------- | -------------------------------------- | ---------------------------- | | **MSO field name** | `_status` | `status` | | **Token header `typ`** | `mattr-statuslist+cwt` | `application/statuslist+cwt` | | **Status bits per credential** | 2 bits | 1 bit | | **Available status values** | Valid, Invalid, Suspended (Deprecated) | Valid, Invalid | | **CBOR payload claim numbers** | Negative keys (-65538, -65539) | Positive keys (65533, 65534) | | **Distribution response format** | Array of objects with `uri` property | Array of URI strings | | **Maximum credentials per list** | 500,000 | 500,000 | Currently all MATTR VII tenants continue to use the legacy specification format by default. In the next release, we will enable the Draft 14 specification for all tenants by default. If you wish your tenant to keep using the legacy specification, please [contact us](mailto:dev-support@mattr.global) to ensure we do not enable the new specification for your tenant. Refer to [mDocs revocation](/docs/issuance/revocation/overview) for more information. ## Analytics event tracking in MATTR VII Management API 2026-03-16 Tags: Platform Management This release of the MATTR VII Management API (v1.29.0) introduces the following enhancement: * **Analytics events for GET /v1/events**: Consistent with all other Management API endpoints, the analytics service now generates events when `GET /v1/events` is called. ## Managed IACA deletion and bug fixes in the MATTR Portal 2026-03-16 Tags: Maintenance, Platform Management, Issuance This release of the MATTR Portal (v1.73.1) introduces the following enhancements and fixes: * **Managed IACA deletion**: Managed IACAs can now be deleted by users with `admin` or `issuer` roles. This provides greater flexibility in managing your certificate authorities and helps maintain a clean and organized certificate inventory. * **Binary claim file upload fix**: Fixed an issue where uploading a file to a Binary claim in Pre-authorized Code flow credential offers included a Data URL prefix (e.g., `data:image/jpeg;base64,`) before the actual base64 content. This prefix caused credential validation failures when adding credentials to certain wallets. The Portal now correctly handles file uploads to ensure only raw base64 content is used in the claim value. * **Form field HTML tag fix**: Fixed an issue where it was possible to add ``, `

`, ``, and `` HTML tags into form fields, which resulted in them being rendered in some delete confirmation modals. ## Maintenance update in MATTR VII Management API 2026-03-02 Tags: Maintenance, Platform Management This release of the MATTR VII Management API (v1.28.2) is a maintenance release. It includes internal changes to enhance performance and usability. ## Singapore region now available 2026-03-02 Tags: Platform Management A new MATTR VII Singapore region is now available, joining the existing regions of New Zealand (Auckland), Australia (Sydney), Europe (Frankfurt), United States (Oregon), and Canada (Montreal). This supports implementations from Singapore that need their infrastructure and data hosted within Singapore boundaries. Customers can now use the MATTR Portal to create new tenants in the new SG region. ## SSO enhancements, JWT access tokens, and analytics improvements in MATTR VII 2026-03-02 Tags: Platform Management, Issuance, Verification This release of the MATTR VII platform (v12.10.0) introduces the following enhancements and fixes: * **Improved SSO user experience**: SSO connection users are no longer prompted to complete MFA enrolment or verification, and are no longer required to verify their email address. This streamlines the sign-in flow for users authenticating via an SSO provider. * **JWT access token from the Token endpoint**: The `POST /v1/oauth/token` endpoint now returns a JWT access token instead of the previously used opaque token. This applies to both the Pre-authorized Code and Authorization Code flows, giving clients richer, self-contained token data. * **Analytics events for GET /v1/events**: Consistent with all other tenant endpoints, the analytics service now generates events when `GET /v1/events` is called. * **Empty credential issuance validation (bug fix)**: In alignment with OID4VCI, the platform now correctly rejects any attempt to issue a credential that contains no claims. Requests of this kind will fail with an appropriate error response. ## Email handling fix in MATTR VII Management API 2026-02-25 Tags: Platform Management, Issuance This release of the MATTR VII Management API (v1.28.1) includes the following fix: * **Email case handling fix (SSO users)**: Fixed an issue where some SSO users with mixed-case email addresses could encounter errors when accepting tenant invites. Email addresses for SSO users are now handled consistently, preventing errors regardless of casing. ## Managed Issuer certificate visibility and accessibility improvements in the MATTR Portal 2026-02-18 Tags: Platform Management, Issuance This release of the MATTR Portal (v1.72.0) introduces the following enhancements: * **Managed Issuer visibility of child certificates**: Managed Issuers can now only view pending and active Document Signer Certificates (DSC) and Status List Signer Certificates (SLSC). They cannot create, edit, or delete DSCs/SLSCs. * **Updated custom domain asset requirements**: Updated the notes on the Custom Domain page to reflect the changes required for the issuer authorization server metadata endpoint. * **Accessibility improvements**: Fixed accessibility issues to improve usability and compliance. ## Client Attestation enhancements in MATTR VII 2026-02-16 Tags: Issuance, Holding This release of the MATTR VII platform (v12.9.0) introduces the following enhancements: * **Client Attestation 'Standard Mode' support**: We've enhanced our Client Attestation capability to support an alternative proof-of-possession method called 'Standard Mode', which sits alongside our existing 'Combined Mode' option. **Standard Mode** is designed for wallets that provide a separate Attestation PoP alongside the Attestation JWT (with or without DPoP), rather than combining the attestation proof with the DPoP proof. This gives wallet developers more flexibility in how they implement client attestation, by offering an option for implementations who prefer not to share the same key for both DPoP and attestation proof. * **Credential Instance ID persistence**: If a holder app chooses to pass a Credential Instance ID when performing Client Attestation with a MATTR VII issuer, we now persist and return this data against the issued credential in the credential registry. This allows issuers to have better traceability of which specific app instance a credential was issued to, and use this information for later analysis or troubleshooting. * **Issuer metadata fix**: Resolved an issue where issuer metadata was not being properly exposed for tenants with a configured custom domain. Client Attestation is currently being offered as a closed beta preview. If you would like to participate, please [contact us](mailto:dev-support@mattr.global). ## Tenant creation restriction in MATTR VII Management API 2026-02-16 Tags: Platform Management This release of the MATTR VII Management API (v1.28.0) introduces the following change: * **Tenant creation restriction**: Users who are invited to existing tenants via the MATTR Portal can no longer create new tenants themselves. This improves governance and ensures tighter control over tenant management. ## Improved tenant governance and maintenance updates in the MATTR Portal 2026-02-11 Tags: Maintenance, Platform Management This release of the MATTR Portal (v1.71.0) introduces the following enhancements: * **Enhanced tenant governance**: Users who are invited to existing tenants via the MATTR Portal can no longer create new tenants of their own, improving governance and control over tenant management. * **Terminology updates**: Renaming of credential formats and related terms across the Portal, along with updates to all credential configuration forms to include consistent messaging and helpful links. * **Improved error reporting**: Sentry has been enabled as an error reporting tool, enhancing data logging and error resolution capabilities. ## Webhook Payload Consistency Fix in MATTR VII 2026-02-04 Tags: Platform Management, Issuance This release of the MATTR VII platform (v12.8.1) resolves an issue where the `OpenIdCredentialIssued` webhook event payload included an unexpected change. The [payload structure](/docs/platform-management/webhooks-guide#event-payload) is now consistent with previous releases. ## New Zealand region now available 2026-02-02 Tags: Platform Management A new MATTR VII New Zealand region is now available, joining the existing regions of Australia (Sydney), Europe (Frankfurt), United States (Oregon), and Canada (Montreal). This supports implementations from New Zealand that need their infrastructure and data hosted within New Zealand boundaries. Customers can now use the MATTR Portal to create new tenants in the new NZ region. ## OID4VCI v1.0 alignment and client attestation closed beta preview in MATTR VII 2026-02-02 Tags: Issuance This release of the MATTR VII platform (v12.8.0) introduces the following enhancements: ### OID4VCI v1.0 alignment [#oid4vci-v10-alignment] MATTR VII now aligns with OpenID for Verifiable Credential Issuance (OID4VCI) v1.0. This enhances interoperability and ensures all relevant parties are following the same specification. The following changes have been implemented: 1. **Separate OAuth server metadata endpoint**: OAuth server metadata, previously exposed via `GET {tenant_url}/.well-known/openid-credential-issuer`, are now available through a dedicated endpoint. See the [authorization server metadata endpoint](/docs/api-reference/platform/issuer-metadata/wellKnownOauthAuthorizationServer) in the API Reference for more information. 2. **Supported credential metadata location change**: Supported credential metadata are now exposed under the `credential_configurations_supported` field (instead of `credentials_supported`) in the response from `GET {tenant_url}/.well-known/openid-credential-issuer`. The new structure contains similar information with an updated format. See the [issuer metadata endpoint](/docs/api-reference/platform/issuer-metadata/wellKnownOidcConfig) in the API Reference for more information. 3. **Credential offer payload change**: In a credential offer payload, the `credentials` field has been replaced with `credential_configuration_ids`. This field includes a list of credential configuration IDs corresponding to the credentials offered to a wallet client. Wallet clients should use these IDs for looking up credential metadata exposed from the credential issuer metadata endpoint. See the [credential offer endpoint](/docs/issuance/credential-offer/api-reference) in the API Reference for more information. 4. **Credential request structure changes**: * `credential_configuration_id` is a new parameter that specifies which credential to issue. This corresponds to the credential configuration ID from the credential offer and the issuer metadata. Previously, this information was included in the `format`, `credential_definition`, `types`, and `doctype` fields (depending on credential format). * `proofs` object provides proof of possession of the cryptographic key material to which the issued credential instances will be bound. This was previously passed in the `proof` parameter (singular). * While the OID4VCI v1.0 structure supports an array of proofs for batch issuances, MATTR VII currently supports a single proof only and will enable batch issuance in the future. * No change is required in the structure of the previously provided `proof` payload. See the [credential issuance endpoint](/docs/api-reference/platform/credential-issuance/postOpenIdCredential) in the API Reference for more information. 5. **Credential response structure change**: The response now contains a `credentials` array of issued credentials (instead of the previous `credential` and `format` elements). * While the OID4VCI v1.0 structure supports an array of credentials for batch issuances, MATTR VII currently supports single credential issuance only and will enable batch issuance in the future. See the [credential issuance endpoint](/docs/api-reference/platform/credential-issuance/postOpenIdCredential) in the API Reference for more information. **Backwards compatibility and deprecation timeline** 1. This change is backwards compatible. Wallet applications making a request using the old structure will receive the old response structures as well. 2. As per our SLA, the previous fields in the API Reference have been marked as deprecated and will reach their End of Life (EOL on May 2nd, 2026), when they will be removed from the platform. You are advised to update your implementation to support this new structure. If you have any concerns about this timeline or need assistance with migration, please [contact us](mailto:dev-support@mattr.global). ### Client attestation closed beta preview [#client-attestation-closed-beta-preview] MATTR VII now offers client attestation as a closed beta preview. Participating customers can: * Configure their tenants so that they will only issue credentials into a wallet that provides DPoP-based client attestation (by adding a client attestation root certificate against a trust wallet list configuration for that tenant). * Pass a client instance ID as part of the client attestation. This identifier will be included in the access token returned to the wallet client. * Use this passed client instance ID as part of a request sent to a configured [claims source](/docs/issuance/claims-source/overview#request-parameters). Refer to [client attestation](/docs/issuance/credential-issuance/wallet-attestation) for more information. If you would like to participate in the closed beta preview, please [contact us](mailto:dev-support@mattr.global). ## SSO authentication and role-based user invitations in MATTR VII 2026-01-27 Tags: Platform Management, Issuance This release of the MATTR VII Management APIs (1.27.0) introduces the following enhancements: * **SSO authentication for Portal access**: The Management API now enables customers to use their Entra ID/Azure AD identity provider (IdP) to authenticate users accessing the MATTR Portal through single sign-on (SSO). This enables organisations to manage Portal access through their existing identity infrastructure, streamlining user authentication and improving security. * **User invitations for managed-issuer role**: Users with the `managed-issuer` role can now invite other users with the same role to the Portal. This enables delegated user management within the same permission scope. If you would like to use your supported SSO provider to access the MATTR Portal in your organisation, please [contact us](mailto:dev-support@mattr.global). ## SSO support and Android verifier configuration now available in the MATTR Portal 2026-01-27 Tags: Platform Management, Verification This release of the MATTR Portal (v1.70.0) introduces the following enhancements: * **SSO support**: Customers can now use their Entra ID/Azure AD identity provider (IdP) to authenticate users accessing the MATTR Portal through single sign-on (SSO). This enables organisations to manage Portal access through their existing identity infrastructure, streamlining user authentication and improving security. * **Android verifier configuration**: You can now use the Portal to manage [Android Verifier Applications](/docs/verification/remote-mobile-verifiers/journey-pattern), enabling you to configure and manage Android mobile app verification workflows. * **New Managed Issuer role**: The Portal now supports the new [`managed-issuer`](/docs/platform-management/roles-and-permissions#managed-issuer) role. This role is similar to the `issuer` role, except it cannot create new IACAs and cannot manage credential configurations. This supports different operational models where issuers are managed by a higher level of admins or issuers who control most of the settings. If you would like to enable SSO for your organisation to access the Portal, please [contact us](mailto:dev-support@mattr.global). ## E2E Encryption spec alignment 2025-12-18 Tags: Issuance This release of the MATTR VII platform (v12.7.2) aligns the End-to-End Encryption (E2E Encryption) credential request format with the OID4VCI specification. ## Bug fix for E2E Encryption in MATTR VII 2025-12-17 Tags: Maintenance, Issuance This release of the MATTR VII platform (v12.7.1) fixes an issue where the JWKS format for [E2E encryption](/docs/issuance/credential-issuance/e2e-encryption) was incorrect in both the credential issuer metadata and credential request format. This fix ensures proper encryption key exchange between issuers and wallets. ## Enhanced security and operational flexibility in MATTR VII 2025-12-15 Tags: Issuance, Holding This release of the MATTR VII platform (v12.7.0) introduces the following enhancements: * **New Managed Issuer role**: The new `managed-issuer` role is similar to the `issuer` role, except it cannot create new IACAs and cannot manage credential configurations. This supports different operational models where issuers are managed by a higher level of admins or issuers who control most of the settings. * **End-to-end encryption for credential issuance**: End-to-End Encryption (E2E Encryption) provides an additional layer of security for credential issuance workflows where intermediary services are involved between the credential issuer and the wallet application. E2E Encryption ensures that credential data and personally identifiable information (PII) remain confidential and unreadable to intermediary backend servers, even as they facilitate the credential delivery process. The credentials are encrypted end-to-end, from the issuer directly to the specific wallet instance, ensuring that only the intended recipient can decrypt and access the credential data. End-to-End Encryption is currently in technical preview and must be enabled on a per-tenant basis. If you would like to enable this feature for your tenant, please [contact us](mailto:dev-support@mattr.global). * **IP address in platform analytics events**: Platform analytics events now include the IP address the request came from. ## MATTR VII Management API maintenance release 2025-12-15 Tags: Maintenance, Platform Management This release of the MATTR VII Management APIs (v1.26.1) is a maintenance release. It includes incremental internal updates to support upcoming features. ## Credential Refresh beta preview in MATTR VII 2025-11-24 Tags: Issuance This release of the MATTR VII platform (v12.6.0) introduces Credential Refresh as a beta preview feature. [Credential Refresh](/docs/issuance/refresh/overview) enables wallets to claim new instances of credentials without requiring the user to authenticate again. This capability supports several use cases, such as validity extension with a streamlined user experience. Credential Refresh is offered as a closed beta feature and is not generally available yet. Functionality may be limited, may not work in all scenarios, and could change or break without prior notice. If you are interested in participating in the closed beta, please [contact us](mailto:dev-support@mattr.global). ## MATTR VII Management API maintenance release 2025-11-24 Tags: Maintenance, Platform Management This release of the MATTR VII Management APIs (v1.26.0) is a maintenance release. It includes internal changes to enhance performance and usability. ## Bug fixes and maintenance improvements in the MATTR Portal 2025-11-13 Tags: Maintenance, Platform Management This release of the MATTR Portal (v1.69.0) introduces the following enhancements: * **Participant points of contact**: Added hint text to recommend a minimum of two points of contact per participant. This release also includes bug fixes and maintenance enhancements to improve overall performance and stability. ## MATTR VII Management API maintenance release 2025-11-10 Tags: Maintenance, Platform Management This release of the MATTR VII Management APIs (v1.25.0) is a maintenance release. It includes internal changes to enhance performance and usability. ## Streamlined configuration of participant document types in MATTR VII 2025-11-10 Tags: Trust Networks This release of the MATTR VII platform (v12.5.0) streamlines configuration of document types ecosystems participants are permitted to issue. When you create or update a participant, you can now define which document types participants are permitted to issue at the IACA level. This enhancement extends the IACA docTypes feature across ecosystem policy and participant specifications, providing more granular control over participant permissions within your ecosystem. ## Revocable mDocs support, enhanced ecosystem management and security improvements in the MATTR Portal 2025-11-06 Tags: Platform Management, Issuance, Trust Networks This release of the MATTR Portal (v1.68.0) introduces the following enhancements: * **Revocable mDocs**: Create credential configurations that let you later change mDoc revocation status (valid, invalid, suspended) for stronger lifecycle control. * **Points of contact**: Manage up to 10 contact records per ecosystem participant. * **Participant evidence PDFs**: Upload, view, replace, and delete PDF evidence linked to an ecosystem participant. * **Simplified ecosystem configuration**: Configure document types directly when uploading an IACA certificate to a participant, simplifying the ecosystem configuration process. This change also removes the ecosystem credential types and participant roles tabs from the Portal. * **Inactivity timeout**: Users are automatically signed out after a period of inactivity to reduce risk from unattended sessions. This release also includes bug fixes and maintenance enhancements to improve overall performance and stability. ## Adding Points of Contact and PDF Evidence to Issuer Participants 2025-10-29 Tags: Issuance, Trust Networks This release of the MATTR VII platform (v12.4.0) introduces the following enhancements to participants records: * **Adding points of contact**: DTS operators can now manage up to 10 records of points of contact for each participant. * **Adding PDF evidence**: DTS operators can now upload and manage PDF files associated with specific participants. ## MATTR VII Management API maintenance release 2025-10-29 Tags: Maintenance, Platform Management This release of the MATTR VII Management APIs (v1.24.0) is a maintenance release. It includes internal changes to enhance performance and usability. ## Enhanced event log filtering in the MATTR Portal 2025-10-23 Tags: Platform Management This release of the MATTR Portal (v1.67.1) introduces enhanced event log capabilities. The Portal now displays the requestor (user ID, client ID, or system) for each event in the event logs, providing better visibility into who initiated each action. You can now filter event logs by requestor to quickly find events associated with specific users, clients, or system processes, making audit trails more transparent and actionable. This release also includes bug fixes and maintenance enhancements to improve overall performance and stability. ## DSC and SLSC revocation via CRL now available in MATTR VII 2025-10-13 Tags: Issuance This release of the MATTR VII platform (v12.3.0) introduces the following enhancements: * **DSC and SLSC revocation via CRL**: Issuers using MATTR-managed IACAs can now revoke their \[Document Signer Certificates] (DSCs) and [Status List Signer Certificates](/docs/issuance/revocation/api-reference/mdocs-status-list-signers#revoke-a-status-list-signer) (SLSCs) via Certificate Revocation List (CRL). This provides greater control over certificate lifecycle management and enhances security by enabling timely revocation of compromised or outdated certificates. * **Updated contact details for DTS participants**: The contact details fields for Digital Trust Service (DTS) participants have been updated to clearly indicate they represent high-level organisation contact details. This change prepares for upcoming features that will allow listing individual points of contact separately, providing more granular contact management capabilities. ## MATTR VII Management API maintenance release 2025-10-13 Tags: Maintenance, Platform Management This release of the MATTR VII Management APIs (v1.23.1) is a maintenance release. It includes internal changes to enhance performance and usability. ## Pre-authorized credential offers and DTS participant field updates in the MATTR Portal 2025-10-13 Tags: Platform Management, Trust Networks This release of the MATTR Portal (v1.66.0) introduces the following enhancements: * **Pre-authorized credential offers**: The Portal now supports creating and testing [pre-authorized credential offers](/docs/issuance/pre-authorized-code/overview), enabling users to configure and validate this issuance flow directly within the Portal. Refer to our [tutorial](/docs/issuance/pre-authorized-code/tutorial) for a hands-on example. * **Updated field names for Ecosystem participants**: Changes have been implemented to support the new field names for Digital Trust Service (DTS) participants introduced in the [latest platform release](#dsc-and-slsc-revocation-via-crl-now-available-in-mattr-vii), ensuring consistency across the platform and Portal experiences. Creating pre-authorized code flow credential offers in the MATTR Portal is for testing purposes only. The populated user ID is generated by the MATTR Portal and cannot be changed to another user later. Sign up for a trial [here](/docs/resources/get-started) and start exploring the MATTR Portal today! ## Enhanced malware scanning for digital pass templates 2025-09-29 Tags: Issuance This release of the MATTR VII platform (v12.2.0) extends our malware scanning capabilities to include zip folders uploaded for digital pass templates. When creating templates for [Apple](/docs/issuance/cwt-credential-templates/apple-templates) and [Google](/docs/issuance/cwt-credential-templates/google-templates) digital passes, any zip folders containing assets (such as images, icons, or other resources) are now automatically scanned for malware before processing. This enhancement strengthens security across the digital pass creation workflow while maintaining the same user experience. ## MATTR VII Management API maintenance release 2025-09-29 Tags: Maintenance, Platform Management This release of the MATTR VII Management APIs (v1.23.0) is a maintenance release. It includes internal changes to enhance performance and usability. ## MATTR VII Management API maintenance release 2025-09-15 Tags: Maintenance, Platform Management This release of the MATTR VII Management APIs (v1.22.1) is a maintenance release. It only includes dependency upgrades. ## MATTR VII Platform maintenance release 2025-09-15 Tags: Maintenance This release of the MATTR VII platform (v12.1.0) is a maintenance release. It introduces internal changes to support future features and improvements. ## Expanded certificate management and automated VICAL scheduling now available in the MATTR Portal 2025-09-01 Tags: Platform Management, Issuance, Trust Networks This release of the MATTR Portal (v1.65.0) introduces enhanced certificate management features and greater automation for credential issuance: * You can now manage a wider range of certificate authorities directly in the Portal, including both externally managed and MATTR-managed: * IACAs * DTS root CA * Verifier root CA * VICAL Configuration now allows you to automatically generate VICALs on a **daily** or **weekly** basis. * Date display enhancements have been introduced: hovering over a date now reveals a popup showing both **local time** and **relative time**, improving clarity for users across time zones. Sign up for a trial [here](/docs/resources/get-started) and start exploring the MATTR Portal today! ## Management API Domain Migration 2025-09-01 Tags: Maintenance, Platform Management This release of the MATTR VII Management APIs (v1.22.0) is a maintenance release. It introduces backend changes required to support new features in the MATTR VII platform. In addition, and in line with our SLA, we are providing an **end-of-life (EOL) notification** for the legacy Management API domain: * The previous domain `https://manage.mattr.global` is now officially **deprecated**. * Customers must complete migration to the new domain `https://manage.au01.mattr.global` before **1 March 2026**. * After this date, the legacy domain will no longer be supported. * This change only impacts customers using **machine clients** to access the Management API. Please update your integrations to ensure continuity of service before the deprecation deadline. ## Support for externally managed Verifier and DTS root CA certificates now available in MATTR VII 2025-09-01 Tags: Issuance, Verification, Trust Networks This release of the MATTR VII platform (v12.0.0) introduces new capabilities for customers managing their own trust infrastructure, providing greater flexibility in establishing trust across your network. You can now integrate with MATTR VII using externally managed certificates from your existing Public Key Infrastructure (PKI): * [Verifier Root CA certificates](/docs/verification/certificates/overview): MATTR VII can now use these to sign **Verification Request Signer Certificates (VRSCs)**, which are then used to sign verification requests. * [DTS Root CA certificates](/docs/digital-trust-service/certificates-overview): MATTR VII can now use these to sign **VICAL Signer Certificates (VSCs)**, which are then used to sign VICALs. These updates enable closer alignment with your existing PKI while still benefiting from MATTR VII’s Digital Trust Service (DTS) and Credential Verification capabilities. ## MATTR VII Management maintenance release 2025-08-18 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.21.0) is a maintenance release. It introduces backend changes required to support new features in the MATTR VII platform. ## Web verifier applications now support dynamic URIs 2025-08-18 Tags: Verification This release of the MATTR VII platform (v11.3.0) introduces support for wildcard path fragments in redirect URIs within verifier applications. Verifier apps can now whitelist [redirect URIs](/docs/verification/remote-verification-api-reference/verifier-applications#create-a-verifier-application!path=0/openid4vpConfiguration/redirectUris\&t=request) that include an asterisk in the path component (e.g., `https://www.example.com/*/callback`), enabling support for dynamic callback paths such as those containing session IDs. ## MATTR Portal maintenance release 2025-08-14 Tags: Maintenance, Platform Management This release of the MATTR Portal (v1.64.0) is a maintenance release. It introduces changes required to support new backend features that are coming up. Sign-up for a trial [here](/docs/resources/get-started) and start exploring the MATTR Portal today! ## Enhanced client analytics tracing in MATTR VII 2025-08-04 Tags: Platform Management, Issuance, Trust Networks This release of the MATTR VII platform (v11.2.0) introduces the following enhancements: * Client activity tracing: You can now filter analytic events by client ID, making it easier to trace specific client actions. * VICAL configuration timestamp: We’ve added visibility into when your VICAL configuration was last set, helping you predict when the next version will be published based on your auto-publish schedule. * New `events` endpoint: A new [`/v1/events`](/docs/platform-management/analytics/api-reference#retrieve-analytic-events) endpoint is available as an alternative to /analytics. This change helps avoid issues with browsers that block requests containing the term "analytics". In alignment with our [Service Level Agreement (SLA)](/docs/resources/terms/service-level-agreement), the existing `/v1/analytics/events` endpoint has been marked as *deprecated*/*retired* and will reach its EOL on February 4th 2026, when it will be removed from the MATTR VII platform. * IACA activation error handling: If you attempt to activate an [unmanaged IACA](/docs/issuance/certificates/guide) that does not have any Document Signer Certificates signed by it, you will now receive a clear error message. To resolve this, activate a Document Signer Certificate signed by the unmanaged IACA before activating it. ## MATTR VII Management maintenance release 2025-08-04 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.20.0) is a maintenance release. It introduces backend changes required to support new features in the MATTR VII platform. ## MATTR Portal maintenance release 2025-07-24 Tags: Maintenance, Platform Management This release of the MATTR Portal (v1.63.0) is a maintenance release. It introduces backend changes required to support new features that are coming up. Sign-up for a trial [here](/docs/resources/get-started) and start exploring the MATTR Portal today! ## MATTR VII Management maintenance release 2025-07-21 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.19.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## VICAL auto-publishing now available in MATTR VII 2025-07-21 Tags: Trust Networks This release of the MATTR VII platform (v11.1.0) introduces the ability to automatically publish your [VICAL](/docs/digital-trust-service/vical-overview) according to a pre-set schedule. The [VICAL configuration](/docs/digital-trust-service/vical-api-reference/configuration) can now be adjusted so that the VICAL is automatically published daily or weekly, ensuring that it is always up-to-date with the latest information without requiring manual intervention. ## HSM support, external Issuer certificates and Verify with Wallet API now available in MATTR VII 2025-07-08 Tags: Issuance, Verification, Holding This release of the MATTR VII platform (v11.0.0) introduces several new features and enhancements: * Hardware Security Module (HSM) support for key management: We’ve introduced support for customers to use a MATTR-managed **Hardware Security Module (HSM)** for key management. This provides an additional layer of assurance for protecting cryptographic keys, beyond our standard **Software Security Module (SSM)**. * **Note:** HSM usage incurs additional costs and requires a commercial agreement. Please reach out to your account team if you’re interested in enabling this feature. * External IACA Support: You now have the flexibility to manage your own **Issuer Authority Certificate Authority (IACA)**, **Document Signing Certificates (DSCs)** and **Status List Signer Certificates (SLSCs)**. This offers flexibility for organizations with specific Public Key Infrastructure (PKI) requirements. It preserves the integrity of the mDoc trust chain while giving issuers full control over their cryptographic materials. Refer to [external issuer certificates](/docs/concepts/chain-of-trust#external-certificates) for more information. * Support for Verify with Apple Wallet API: We’ve added the necessary configuration to support the [Verify with Wallet API](https://developer.apple.com/wallet/get-started-with-verify-with-wallet/) in iOS mobile apps, including a required **Apple Team ID**. This sets the stage for upcoming enhancements in our [iOS Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) that will enable streamlined credential verification flows in mobile applications. * Enhanced Credential Metadata in API Responses: We’ve enriched the data returned when [retrieving users' credentials metadata](/docs/api-reference/platform/users/getUserCredentials) with new fields such as *Issuance Date*, *Valid From*, and *Valid Until*. These additions provide greater visibility into credential lifecycles. ## Support for external Issuer certificates and Verify with Wallet API now available in MATTR Portal 2025-07-08 Tags: Platform Management, Issuance, Verification, Holding This release of the MATTR Portal (v1.62.2) introduces changes to support new capabilities offered by the MATTR VII platform: * To comply with changes made to support the Verify with Apple Wallet API, when creating an iOS Verifier Application the following changes are now in effect: * The Team ID property is required. * The OID4VP configuration is optional. * To comply with changes made to enable external issuer certificates, IACAs are always created as inactive by default, and you must manually set them to active after they are created. Sign-up for a trial [here](/docs/resources/get-started) and start exploring the MATTR Portal today! ## MATTR VII Management maintenance release 2025-07-07 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.18.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## MATTR VII Management maintenance release 2025-06-24 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.17.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## Use MATTR VII to configure explicit validity periods for issued mDocs 2025-06-24 Tags: Issuance This release of the MATTR VII platform (v10.4.0) introduces the ability to [create mDocs credential configurations](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration) that define explicit: * *Activation date* using the [`validFrom`](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=validFrom\&t=request) parameter. This allows issuers to create mDocs that are valid from an explicit future date, enabling scenarios where credentials can be issued in advance and activated later. * *Expiration date* using the [`validUntil`](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=validUntil\&t=request) parameter. This allows issuers to set a specific end date for the validity of mDocs credentials, ensuring that credentials are only valid for a defined period. ## UX enhancements and bug fixes in the MATTR Portal 2025-06-17 Tags: Maintenance, Platform Management This release of the MATTR Portal (v1.61.1) allows you to paste string data directly into form fields that accept PEM files as identifiers. This update also includes various bug fixes and enhancements to improve the overall user experience. Sign-up for a trial [here](/docs/resources/get-started) and start exploring the MATTR Portal today! ## Enhanced OID4VCI flows traceability in MATTR VII 2025-06-09 Tags: Issuance This release of the MATTR VII platform (v10.3.0) introduces enhanced traceability for [OID4VCI flows](/docs/issuance/oid4vci-overview). This includes adding parameters to relevant analytics events, enabling better tracking and analysis of platform events. The following analytic events have been updated to include the `sessionId` parameter: * Create a Pre-authorized Code offer: * `OPENID_PRE_AUTHORIZED_OFFER_CREATE_START` * `OPENID_PRE_AUTHORIZED_OFFER_CREATE_SUCCESS` * Delete a Pre-authorized Code offer: * `OPENID_PRE_AUTHORIZED_OFFER_DELETE_START` * `OPENID_PRE_AUTHORIZED_OFFER_DELETE_SUCCESS` * If the provided `offerId` does not exist, the `OPENID_PRE_AUTHORIZED_OFFER_DELETE_FAIL` will include the `offerId`. * Authorization Code flow: * `OPENID_AUTHORIZE_SUCCESS` * `OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_SUCCESS` * `OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_START` * If the user fails to authenticate with the configured authentication provider, the `OPENID_AUTHENTICATION_PROVIDER_AUTHORIZE_FAIL` event will also include the `sessionId`. * If the tenant fails to authenticate with a configured claim source, the `OPENID_CLAIM_AUTHORIZATION_FAIL` event will include the `sessionId`. Refer to the [MATTR VII Events registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) for a complete list of MATTR VII Platform API events and their parameters. ## MATTR VII Management maintenance release 2025-06-09 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.16.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## Mobile app verification now available in the MATTR Portal 2025-05-27 Tags: Platform Management, Verification This release of the MATTR Portal (v1.60.0) enables you to configure and manage [mobile app verification](/docs/verification/remote-mobile-verifiers/workflow) workflows in the MATTR Portal. This includes: * What mobile applications can interact with the MATTR VII tenant. * What wallet applications can present credentials. * What issuers can be trusted. Sign-up for a trial [here](/docs/resources/get-started) and start exploring the MATTR Portal today! ## MATTR VII Management maintenance release 2025-05-26 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.15.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## New issuance and verification channels supported in MATTR VII 2025-05-26 Tags: Issuance, Verification This release of the MATTR VII platform (v10.2.0) introduces new issuance and verification channels: * MATTR VII now supports issuing credentials using the [OID4VCI Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview). This flow enables issuers to authenticate and authorize holders in advance, making credential issuance faster and more streamlined for users who have already been verified. To support this, you can now manually [create new users](/docs/api-reference/platform/users/createUser) and generate [credential offers](/docs/issuance/credential-offer/overview) for them. * By integrating MATTR VII with the MATTR Pi [iOS mDocs Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview), you can build iOS apps that remotely request and verify mDocs presented from another app on the same device. This supports workflows defined by [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). ## MATTR Portal now available for trial users 2025-05-13 Tags: Platform Management This release of the MATTR Portal (v1.59.0) makes the portal available to trial users. Trial users can now access the Portal and explore the features of the MATTR VII platform through an intuitive graphical user interface. Sign-up for a trial [here](/docs/resources/get-started) and start exploring the MATTR Portal today! ## Enhanced support for trial users in the Management API 2025-05-12 Tags: Platform Management This release of the MATTR VII Management API (v1.14.0) introduces enhanced support for trial users. This will enable users to more easily [trial](/docs/resources/get-started) MATTR VII and the [MATTR Portal](/docs/platform-management/portal). ## New user information available in MATTR VII 2025-05-12 Tags: Issuance This release of the MATTR VII platform (v10.1.0) enhances the [user retrieval](/docs/api-reference/platform/users/getUsers) operations to include linked account data in the response. This allows you to view and manage identity connections more easily through a single API call, enabling advanced user interactions. Additionally, requests to external [claim sources](/docs/issuance/claims-source/overview) have been improved for greater reliability. Outbound requests now explicitly use IPv4, preventing resolution failures that previously occurred when some services defaulted to unsupported IPv6. ## MATTR Portal user interface enhancements 2025-05-06 Tags: Platform Management This release of the MATTR Portal (v1.58.0) introduces user interface enhancements to improve the overall user experience. This includes refinements to the layout, navigation, and styling of various components within the Portal. Additionally, this release includes backend changes to support exciting new features that are coming up. The Portal is available to selected cloud environments. [Contact us](https://mattr.global/contact) if you're interested in accessing these features or learning more. ## Role based access control now available in the MATTR Portal 2025-04-16 Tags: Platform Management This release of the MATTR Portal (v1.57.1) introduces Role Based Access Control (RBAC) capabilities. This enables admins to manage permissions and access within specific MATTR VII tenants, to ensure users can only access the functionalities they need to perform their role. This feature enables you to use the Portal to manage [users](/docs/platform-management/portal#inviting-users) and [clients](/docs/platform-management/create-client) that can access your tenants. The Portal UI is aligned with the user's role and the permissions assigned to them. This means that users will only see the features and functionalities that are relevant to their role. This is particularly useful for organizations with multiple users who have different responsibilities and permissions. Refer to [Access control](/docs/platform-management/access-control) for more information on available roles and associated permissions. The Portal is available to selected cloud environments. [Contact us](https://mattr.global/contact) if you're interested in accessing these features or learning more. ## MATTR VII Management maintenance release 2025-04-14 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.13.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## Refined certificate capabilities in MATTR VII 2025-04-14 Tags: Issuance This release of the MATTR VII platform (v10.0.0) introduces the following changes to certificate management in MATTR VII: * Added safeguards to prevent the use of non-compliant DSCs (originally created for signing non-mDL credentials) for mDL signing, particularly when their validity period exceeds the specification limit. Validation has also been added to the credential configurations endpoint to ensure early failure for such cases (attempting to sign credentials with validity periods longer than 427 days for mDLs or 3620 days for any other mDoc). Refer to [certificate selection](/docs/issuance/certificates/overview#certificate-selection) for more information. * Updated IACA validation logic to allow the use of [user-assigned country codes](https://www.iso.org/glossary-for-iso-3166.html). You can now successfully create and validate IACAs with these codes. ## MATTR VII Management maintenance release 2025-03-31 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.12.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## MATTR VII Platform maintenance release 2025-03-31 Tags: Maintenance This release of the MATTR VII platform (v9.1.0) is a maintenance release. It introduces internal enhancements and bug fixes to improve overall platform performance. ## User experience enhancements in the MATTR Portal 2025-03-27 Tags: Platform Management, Verification, Trust Networks This release of the MATTR Portal (v1.56.0) introduces the following user experience enhancements: * Refined fields names and descriptions in the Verifier application form for improved clarity. * Renamed the **Ecosystem** navigation item to **Digital Trust Service** for better alignment with service scope. * Updated breadcrumb links to correctly direct users when navigating to pages with multiple tabs. * Various bug fixes and minor enhancements. The Portal is available to selected cloud environments. [Contact us](https://mattr.global/contact) if you're interested in accessing these features or learning more. ## MATTR VII Management maintenance release 2025-03-17 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.11.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## Updated mDocs validity periods 2025-03-17 Tags: Issuance This release of the MATTR VII platform (v9.0.0) introduces a change to the maximum validity of issued mDocs to improve the efficiency of signer certificate management. When issuing mDocs and setting `type` to `org.iso.18013.5.*.mDL` (issuing an mDL) the maximum validity is now the shorter of 427 days or the remaining IACA validity period. For any other `type` the maximum validity is now the shorter of 10 years minus 30 days or the remaining IACA validity period. ## MATTR VII Management maintenance release 2025-03-03 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.10.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## Role based access control available in MATTR VII 2025-03-03 Tags: Platform Management, Verification This release of the MATTR VII platform (v8.0.0) introduces Role Based Access Control (RBAC) capabilities. This enables admins to manage permissions and access within specific MATTR VII tenants, to ensure users can only access the functionalities they need to perform their role. Refer to [Access control](/docs/platform-management/access-control) for more information and to the corresponding [tutorial](/docs/platform-management/access-control-tutorial) for a hands-on example. Additionally, following our EOL announcement and in alignment with our [Service Level Agreement (SLA)](/docs/resources/terms/service-level-agreement), we have removed support for the BBS 2020 signature suite. This means that JSON credentials supporting selective-disclosure can only be: * Signed using the BBS 2022 signature suite. * Verified if they were signed using the BBS 2022 signature suite. ## Deprecation of non-hyphenated endpoints 2025-02-26 Tags: Maintenance Following our [EOL announcement](#enhancements-and-improvements-across-different-mattr-vii-capabilities), and in alignment with our [Service Level Agreement (SLA)](/docs/resources/terms/service-level-agreement), we have removed support for several non-hyphenated endpoints, and replaced them with hyphenated endpoints for better readability: * `/v2/credentials/compact/digital-pass/apple` replaced `/v2/credentials/compact/digitalpass/apple`. * `/v2/credentials/compact/digital-pass/apple/templates` replaced `/v2/credentials/compact/digitalpass/apple/templates`. * `/v2/credentials/compact/digital-pass/google` replaced `/v2/credentials/compact/digitalpass/google`. * `/v2/credentials/compact/digital-pass/google/templates` replaced `/v2/credentials/compact/digitalpass/google/templates`. * `/v2/credentials/compact-semantic/digital-pass/apple` replaced `/v2/credentials/compact-semantic/digitalpass/apple`. * `/v2/credentials/compact-semantic/digital-pass/apple/templates` replaced `/v2/credentials/compact-semantic/digitalpass/apple/templates`. * `/v2/credentials/compact-semantic/digital-pass/google` replaced `/v2/credentials/compact-semantic/digitalpass/google`. * `/v2/credentials/compact-semantic/digital-pass/google/templates` replaced `/v2/credentials/compact-semantic/digitalpass/google/templates`. * `/v1/users/authentication-providers` replaced `/v1/users/authenticationproviders`. * `/v1/claim-sources` replaced `/v1/claim-sources`. * `/v2/credentials/mobile/document-signers` replaced `/v2/credentials/mobile/document-signers`. * `/v2/credentials/web-semantic/linked-data/convert` replaced `/v2/credentials/web-semantic/linkeddata/convert`. ## Expanded support for online presentations signing protocols in MATTR VII 2025-02-17 Tags: Verification This release of the MATTR VII platform (v7.1.0) expands the support for online presentations signing protocols in MATTR VII. [mDocs online presentations](/docs/verification/remote-overview) can now be signed by presenting devices using Device MAC Authentication. This is in addition to the existing support for signing a presentation using Digital Signatures. ## MATTR VII Management maintenance release 2025-02-17 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.9.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## User experience enhancements in the MATTR Portal 2025-02-17 Tags: Platform Management This release of the MATTR Portal (v1.55.0) introduces the following user experience enhancements: * The Custom domain configuration screen is now located in a dedicated page. It is accessible from the main navigation panel. * The Portal authentication domain is now `auth.manage.mattr.global`. The Portal is available to selected cloud environments. [Contact us](https://mattr.global/contact) if you're interested in accessing these features or learning more. ## MATTR VII Management maintenance release 2025-02-03 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.8.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## mDocs online verification configuration now supports future-dated IACAs 2025-02-03 Tags: Issuance, Verification This release of the MATTR VII platform (v7.0.0) introduces support for future-dated IACAs as part of mDoc online verification workflows configuration. When setting up a new trusted issuer, you now have the option to add a PEM representing an IACA with a future activation date. This allows issuers to distribute their IACAs' PEM certificates ahead of time and relying parties to seamlessly switch to a new one when required, ensuring continuous operation without downtime. *Breaking changes* We've made changes to the `identifiers.mobile` element of the request body structure in the [create](/docs/api-reference/platform/participants/createEcosystemParticipant) and [update](/docs/api-reference/platform/participants/updateEcosystemParticipant) participant endpoints. Refer to the provided links for the new structure. ## Online verification configuration now available in the Self Service Portal 2025-01-23 Tags: Platform Management, Verification This release of the MATTR Portal (v1.53.0) introduces the capability to use the portal to configure [online mDocs verification workflows](/docs/verification/remote-web-verifiers/workflow) on your MATTR VII tenants. This includes configuring and managing: * What verifier applications can interact with the MATTR VII tenant. * What wallet applications can present mDocs for online verification. * What issuers can be trusted when verifying mDocs presented online. The Self Service Portal is available to selected cloud environments. [Contact us](https://mattr.global/contact) if you're interested in accessing these features or learning more. ## mDocs Online presentation bug fix in MATTR VII 2025-01-21 Tags: Maintenance, Verification This release of the MATTR VII platform (v6.0.1) fixes up a bug that prevented multiple verifier applications from using different application configurations. This issue has been resolved, and each application can now use a specific [application configuration](/docs/verification/remote-verification-api-reference/verifier-applications#create-a-verifier-application). ## MATTR VII Management maintenance release 2025-01-08 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.7.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## Multiple Verifier applications support in MATTR VII 2025-01-08 Tags: Verification This release of the MATTR VII platform (v6.0.0) introduces support for multiple verifier applications interacting with a single MATTR VII tenant to perform [online verification](/docs/verification/remote-web-verifiers/workflow) of [mDocs](/docs/concepts/mdocs). This means that a single MATTR VII tenant can now be configured to handle mDocs online verification requests from different web applications, and handle these requests differently based on the requesting application. **Breaking changes** * A [new endpoint](/docs/verification/remote-verification-api-reference/verifier-applications#create-a-verifier-application) is now available to configure a verifier application, replacing the verifier configuration endpoint. * A [new endpoint](/docs/verification/certificates/api-reference/verifier-root-ca-certificates#create-a-verifier-root-ca-certificate) is now available to create a verifier root CA certificate, replacing the verifier configuration endpoint. * When performing online verification of mDocs, the credential status is now returned in the `status` property of the response (previously it was returned in `status.type`). ## Enhanced certificate capabilities in MATTR VII 2024-12-09 Tags: Issuance This release of the MATTR VII platform (v5.0.0) introduces the following key enhancements to X.509 certificates capabilities in MATTR VII: * To support IACA [rotation](/docs/concepts/chain-of-trust#certificates-rotation), tenants can now have multiple IACAs. This allows issuers to create new IACAs and distribute them to relying parties in advance to ensure continuous operation of issuance and verification workflows. * Generation of [Document Signer Certificates (DSCs)](/docs/issuance/certificates/overview#document-signer-certificate-dsc) is now handled automatically by MATTR VII, ensuring issuance workflows don't break due to DSC expiry. * DSCs now include a Subject Alternative Name field to improve their alignment with the [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) standard. Additional features included in this release: * The [online presentation cross-device modal](/docs/verification/remote-web-verifiers/workflow#the-link-is-used-to-invoke-a-compliant-digital-wallet) now closes when the user clicks outside of it. * Deprecation of the following Ecosystem endpoints: * Retrieve Issuer's Policy. * Retrieve Verifier's Policy. * Retrieve Policy. These endpoints were replaced by a single endpoint to retrieve a consolidated ecosystem policy. ## MATTR VII Management maintenance release 2024-12-09 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.6.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## Improved user experience in the Self Service Portal 2024-12-03 Tags: Platform Management The new version of the Self Service Portal (v1.52.0) introduces an enhanced navigation sidebar for improved user experience. Features are now organized by user roles, enabling faster and more intuitive access to portal functionalities. The Self Service Portal is available to selected cloud environments. [Contact us](https://mattr.global/contact) if you're interested in accessing these features or learning more. ## MATTR VII Management maintenance release 2024-11-25 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.5.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## mDocs revocation now available in MATTR VII 2024-11-25 Tags: Issuance This release of the MATTR VII platform (v4.4.0) introduces support for mDocs revocation. Issuers can now issue mDoc in a way that enables them to later change their status between `valid`, `suspended` and `invalid`. Relying parties can retrieve this status and use it in their verification workflows. Refer to mDocs [revocation](/docs/issuance/revocation/overview) for more information. Additional features included in this release: * [Document Signer Certificates (DSCs)](/docs/issuance/certificates/overview#document-signer-certificate-dsc) can now be created with validity of up to 10 years. * When attempting to sign an mDoc which has `DocType` set to `org.iso.18013.5.*.mDL` (where `*` is a positive integer), MATTR VII recognizes that this is attempt to sign an mDL and will validate that the DSC used to sign it isn't valid for more than 457 days to comply with [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). ## Self Service Portal Maintenance release 2024-11-14 Tags: Maintenance, Platform Management The new version of the Self Service Portal (v1.51.0) is a maintenance release. It includes backend changes required for exciting new features that are coming up. The Self Service Portal is available to selected cloud environments. [Contact us](http://mattr.global/contact) if you're interested in accessing these features or learning more. ## CRL distribution points now optional in MATTR VII 2024-11-11 Tags: Issuance, Trust Networks This release of the MATTR VII platform (v4.3.0) introduces relaxed validation rules when adding external IACAs. The following IACAs were previously rejected by MATTR VII but can now be accepted: * IACAs without a Certificate Revocation List (CRL) distribution endpoint. * IACAs without a `pathLenConstraint` basic constrain. These IACAs can now be added as the root certificates of: * Trusted mDoc issuers. * Ecosystem participants. ## MATTR VII Management maintenance release 2024-11-11 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.4.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## MATTR VII Management API maintenance release 2024-10-29 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.3.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## MATTR VII Platform maintenance release 2024-10-29 Tags: Maintenance This release of the MATTR VII platform (v4.2.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## Removal of DSC functionalities from the Self Service Portal 2024-10-23 Tags: Platform Management, Issuance This release of the Self Service Portal (v1.50.0) removes [Document Signer Certificate (DSC)](/docs/issuance/certificates/overview#document-signer-certificate-dsc) functionalities from the website, as these are going to be handled automatically by the MATTR VII tenant. Additionally, this release includes miscellaneous maintenance changes and bug fixes. The [Self Service Portal](/docs/platform-management/portal) is available to selected cloud environments. [Contact us](https://mattr.global/contact) if you're interested in accessing these features or learning more. ## Terminology updates across MATTR platforms 2024-10-15 Tags: Maintenance To make it easier to consume our capabilities, we have made some changes to our terminology to describe credentials supported by MATTR platforms by their underlying technology and standards. The following credential formats are supported: * [mDocs](/docs/concepts/mdocs): Previously referred to as Mobile credentials. * [CBOR Web Tokens (CWT) credentials](/docs/concepts/cwt): Previously referred to as Compact credentials. * JSON credentials: Previously referred to as Web credentials. ## Ecosystem and certificate enhancements in MATTR VII 2024-10-14 Tags: Issuance, Trust Networks This release of the MATTR VII platform (v4.1.0) introduces improvements to our certificates and Ecosystem capabilities: * When retrieving Ecosystem integrations, you can now view the time when the integration was last synced at. * Improved validation logic now prevents different IACA certificates that only differ by whitespaces and/or line breaks. ## Management API maintenance release 2024-10-14 Tags: Maintenance, Platform Management This release of the MATTR VII management APIs (v1.2.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## VICAL Capabilities in the Self Service Portal 2024-10-10 Tags: Platform Management, Trust Networks The new version of the [Self Service Portal](/docs/platform-management/portal) (v1.49.0) introduces new certificate and [VICAL](/docs/digital-trust-service/vical-overview) management capabilities. You can now use the portal to: * Create [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) certificates. * Create and manage VICAL configurations. * View more details when inspecting VICAL previews. Additionally, this release includes miscellaneous maintenance changes. The [Self Service Portal](/docs/platform-management/portal) is available to selected cloud environments. [Contact us](http://mattr.global/contact) if you're interested in accessing these features or learning more. ## Mobile Credentials online verification now available in MATTR VII 2024-09-30 Tags: Issuance, Verification This release of the MATTR VII platform (v4.0.0) introduces new capabilities to support online verification of Mobile Credentials, as per [ISO/IEC 18013-7](https://www.iso.org/standard/91154.html). You can now use a MATTR VII tenant together with the [Verifier Web SDK](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/index.html) to verify [Mobile Credentials](/docs/concepts/mdocs) presented online via both [same-device](/docs/verification/remote-overview#same-device-verification-workflow) and [cross-device](/docs/verification/remote-overview#cross-device-verification-workflow) flows. Learn more about the [online verification flow](/docs/verification/remote-overview) and its [implementation](/docs/verification/remote-web-verifiers/workflow). ## Enhanced certificate parsing 2024-09-25 Tags: Issuance This release of the MATTR VII platform (v3.9.1) introduces an improvement to the parsing of X.509 certificates, so that the certificate's `issuingAuthority` can now be displayed in a human readable format. ## Expanded Ecosystem capabilities in the Self Service Portal 2024-09-17 Tags: Platform Management, Trust Networks The new version of the Self Service Portal (v1.48.0) introduces new [Ecosystem](/docs/digital-trust-service) and [certificates](/docs/concepts/chain-of-trust) management capabilities. You can now use the Self Service Portal to: * Create, update and delete an [Ecosystem](/docs/digital-trust-service/vical-guide#create-an-ecosystem). * View and download previously generated [VICALs](/docs/digital-trust-service/vical-overview). * View the `stateOrProvinceName` and `country` fields for [IACAs](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) and [DSCs](/docs/issuance/certificates/overview#document-signer-certificate-dsc). The Self Service Portal is available to selected cloud environments. [Contact us](http://mattr.global/contact) if you're interested in accessing these features or learning more. ## New ecosystem capabilities and online presentation tech preview 2024-09-16 Tags: Verification, Trust Networks This release of the MATTR VII platform (v3.9.0) introduces the following new capabilities: * [Ecosystem](/docs/digital-trust-service) operators can now integrate external trusted sources into their own ecosystem policy. These can be either a different Ecosystem or a [VICAL](/docs/digital-trust-service/vical-overview). These external sources are then integrated into the [ecosystem policy](/docs/digital-trust-service/vical-guide#manually-publish-a-vical) when it is published and consumed by relying parties. * Various enhancements required for the tech preview of Mobile Credentials online presentation. [Contact us](https://mattr.global/contact-us) if you are interested in this capability. ## MATTR VII Maintenance release 2024-08-29 Tags: Maintenance This release of the MATTR VII platform (v3.8.0) is a maintenance release. It introduces backend changes required to support exciting new features that are coming up. ## New Ecosystem capabilities in the Self Service Portal 2024-08-29 Tags: Platform Management, Trust Networks The new version of the Self Service Portal (v1.47.0) introduces new [Ecosystems](/docs/digital-trust-service) management capabilities. Ecosystem operators can now use the Self Service Portal to: * Manage [Ecosystem participants](/docs/digital-trust-service/vical-guide#create-participants-and-add-issuer-certificates). * Preview and generate a [VICAL](/docs/digital-trust-service/vical-overview) based on their [Ecosystem policy](/docs/digital-trust-service/vical-guide#manually-publish-a-vical). The Self Service Portal is available to selected cloud environments. [Contact us](http://mattr.global/contact) if you're interested in accessing these features or learning more. ## Enhanced Ecosystem participant validation 2024-08-19 Tags: Trust Networks This release of the MATTR VII platform (v3.7.0) introduces enhanced Ecosystem [participants](/docs/digital-trust-service/vical-guide#create-participants-and-add-issuer-certificates) validation. With this update, when a participant is [created](/docs/api-reference/platform/participants/createEcosystemParticipant) with a `country` and/or `stateOrProvince` fields, these must match the corresponding fields in the IACA certificate used as the `mobile` identifier for this participant. ## Self Service Portal Maintenance release 2024-08-13 Tags: Maintenance, Platform Management The new version of the Self Service Portal (v1.46.0) is a maintenance release. It tidies up various UI elements, hint texts and external links, while introducing some backend changes required for exciting new features that are coming up. The Self Service Portal is available to selected cloud environments. [Contact us](http://mattr.global/contact) if you're interested in accessing these features or learning more. ## Updates to Mobile Credential configurations claim types 2024-08-12 Tags: Issuance This release of the MATTR VII platform (v3.6.0) introduces the following changes to the Mobile Credential `org.iso.18013.5.1.driving_privileges` claim type: * Mapping data into either the `issue_date` or `expiry_date` items in the `org.iso.18013.5.1.driving_privileges` claim is now optional. * You can now map data into a `codes` array within the `org.iso.18013.5.1.driving_privileges` claim. This enables including specific driving privileges code information in the Mobile Credential. ## Enhancements and improvements across different MATTR VII capabilities 2024-08-05 Tags: Issuance, Trust Networks This release of the MATTR VII platform (v3.5.0) includes several enhancements and features, which now enable you to: * Issue [Compact Credentials](/docs/concepts/cwt) in a [revoked](/docs/issuance/revocation/overview) state, so that they only become valid once you manually change their revocation status. Refer to the [Compact Credentials direct issuance guide](/docs/issuance/cwt-direct-issuance) for more information. * Populate new fields when creating [Ecosystem participants](/docs/digital-trust-service/vical-guide#create-participants-and-add-issuer-certificates). These optional fields include the participant's status (active/inactive), country, state/province and contact details. * Define an issuer's state/province when creating their [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca), as per [ISO 18013-5](https://www.iso.org/standard/69084.html). Refer to the [IACA creation guide](/docs/issuance/certificates/guide) for more information. In addition to these new features, in this release we have introduced alternative paths to some of our existing endpoints, where we added hyphens for better readability. This is not a breaking change as the non-hyphenated paths are still supported. However, in accordance with the terms of our [Service Level Agreement (SLA)](/docs/resources/terms/service-level-agreement), the non-hyphenated paths are now marked as **"Retired"**, and will reach their EOL on February 5th 2025, when they will be removed from the MATTR VII platform. You are advised to adjust your implementation to use the following new paths prior to the EOL date: * Use `/v2/credentials/compact/digital-pass/apple` to replace `/v2/credentials/compact/digitalpass/apple`. * Use `/v2/credentials/compact/digital-pass/apple/templates` to replace `/v2/credentials/compact/digitalpass/apple/templates`. * Use `/v2/credentials/compact/digital-pass/google` to replace `/v2/credentials/compact/digitalpass/google`. * Use `/v2/credentials/compact/digital-pass/google/templates` to replace `/v2/credentials/compact/digitalpass/google/templates`. * Use `/v2/credentials/compact-semantic/digital-pass/apple` to replace `/v2/credentials/compact-semantic/digitalpass/apple`. * Use `/v2/credentials/compact-semantic/digital-pass/apple/templates` to replace `/v2/credentials/compact-semantic/digitalpass/apple/templates`. * Use `/v2/credentials/compact-semantic/digital-pass/google` to replace `/v2/credentials/compact-semantic/digitalpass/google`. * Use `/v2/credentials/compact-semantic/digital-pass/google/templates` to replace `/v2/credentials/compact-semantic/digitalpass/google/templates`. * Use `/v1/users/authentication-providers` to replace `/v1/users/authenticationproviders`. * Use `/v1/claim-sources` to replace `/v1/claim-sources`. * Use `/v2/credentials/mobile/document-signers` to replace `/v2/credentials/mobile/document-signers`. * Use `/v2/credentials/web-semantic/linked-data/convert` to replace `/v2/credentials/web-semantic/linkeddata/convert`. ## MATTR VII Management API release 2024-08-01 Tags: Platform Management The new version of the MATTR VII Management API (v1.1.0) introduces new internal capabilities to facilitate tenant off-boarding. Please [contact us](http://mattr.global/contact-us) if you are interested in these capabilities. ## Self Service Portal Maintenance release 2024-07-25 Tags: Maintenance, Platform Management The new version (v1.45.0) of the Self Service Portal is a maintenance release. The Self Service Portal is available to selected cloud environments. [Contact us](http://mattr.global/contact) if you're interested in accessing these features or learning more. ## Enhanced validation and certificate management in MATTR VII 2024-07-22 Tags: Issuance This release of the MATTR VII platform (v3.4.0) includes the following enhancements: * Binary [claim types](/docs/issuance/credential-configuration/overview#claims-mapping) in [Mobile Credential configurations](/docs/issuance/credential-configuration/overview) are now validated to be in a `base64` format. This creates a more robust solution, as binary claim types that are formatted differently would cause credential issuance to fail. * Deleting [IACA certificates](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) now returns an error if any of the [DSC child certificate](/docs/issuance/certificates/overview#document-signer-certificate-dsc) keys were not removed. ## MATTR VII Management API security enhancements 2024-07-17 Tags: Maintenance, Platform Management The Management API offers a set of actions beyond the scope of a single tenant or environment. This release includes an upgrade of several dependencies to mitigate potential security vulnerabilities. ## Improved x509 certificate validations and usability in MATTR VII 2024-07-08 Tags: Issuance X.509 certificates are a standardized format for digital certificates. [IACAs](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) and [DSCs](/docs/issuance/certificates/overview#document-signer-certificate-dsc), used to sign and verify Mobile Credentials, are both X.509 certificates. This release of the MATTR VII platform (v.3.3.0) improves validation and usability of X.509 certificates: * Uploaded PEM certificates are now validated. New IACAs and DSCs cannot be created with invalid PEMs. * DSC expiry dates (`notBefore` and `notAfter`) are now parsed to ensure they match the required `Date` type. * By default DSCs can be created with a maximal validity of 457 days. You can now request to adjust this limit on a per-tenant basis. ## Certificates now available in the Self Service Portal 2024-07-04 Tags: Platform Management, Issuance You can now use the Self Service Portal to view certificates available on your tenant. This includes [DSCs](/docs/issuance/certificates/overview#document-signer-certificate-dsc) and [IACAs](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) which are used to issue and verify [Mobile Credentials](/docs/concepts/mdocs). The Self Service Portal is available to selected cloud environments. [Contact us](http://mattr.global/contact) if you're interested in accessing these features or learning more. ## Improved did:web interoperability 2024-06-27 Tags: Issuance This release of the MATTR VII platform (v3.2.0) improves `did:web` interoperability by supporting usage of a single `did:web` by multiple issuance systems, all issuing credentials on behalf of a single credential issuer. ## Security enhancements and bug fixes 2024-06-24 Tags: Maintenance This release of the MATTR VII platform (v3.1.0) enhances security of integrating Claims sources by extending the current URL validation to also cover any redirects that are included in a configured Claims source URL. This release also includes miscellaneous bug fixes and usability enhancements, as well as required modifications to support future functionalities. ## Introducing Webhook enhancements, the Events registry and Semantic versioning 2024-06-10 Tags: Platform Management, Issuance, Verification ### Webhook enhancements [#webhook-enhancements] The following enhancements to the Webhook capabilities are now available: The event payload now includes a `userClaims` object, which contains all the claims persisted on your tenant as part of the issuance workflow. Refer to [Webhooks payload](/docs/platform-management/webhooks-guide#event-payload) for more information. You can now subscribe to a new `OpenIdCredentialIssuedSummary` event type. This event is triggered upon the completion of an [OID4VCI issuance flow](/docs/issuance/oid4vci-overview) but only provides a summary of the issuance event, leaving out the `credential` object. Refer to [Webhooks](/docs/platform-management/webhooks-overview) for more information. The [endpoint](/docs/platform-management/webhooks-api-reference#retrieve-webhook-jwks) that is used to retrieve public keys MATTR VII uses to sign the Webhook HTTP requests is now publicly available. This makes it easier for relying parties to verify incoming Webhook requests. Refer to [Verify a Webhook](/docs/platform-management/webhooks-guide#verify-webhook-requests) for more information. ### Events registry [#events-registry] The [Events registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) is a publicly available comprehensive collection of analytic events generated by the MATTR VII platform. Events are grouped by the service that generates them, which corresponds to the event category. The registry details the structure of different event payloads based on the configured logging level. Refer to [Events structure](/docs/platform-management/analytics/overview#events-structure) for more information. ### Semantic versioning [#semantic-versioning] To increase transparency and simplify support and maintenance workflows, this release introduces versioning to MATTR VII, following the [Semantic Versioning](https://semver.org/) specification. This current release is tagged as version `3.0.0`. ## Configure a Webhook in the Self Service Portal 2024-06-06 Tags: Platform Management You can now use the MATTR VII [Self Service Portal](/docs/platform-management/portal) to configure a [Webhook](/docs/platform-management/webhooks-overview) for a tenant in your environment. You can subscribe to specific events that are triggered on set MATTR VII operations to retrieve the required information whenever it is generated. The Self Service Portal is available to selected cloud environments. [Contact us](http://mattr.global/contact) if you're interested in accessing these features or learning more. ## Configure a Custom domain in the Self Service Portal 2024-05-21 Tags: Platform Management You can now use the MATTR VII [Self Service Portal](/docs/platform-management/portal) to configure a [Custom domain](/docs/platform-management/custom-domain-overview) for a tenant in your environment. Custom domains represent your known and trusted brand, and can assist in instilling trust with your end-users when they interact with your MATTR VII tenant. Custom domains don't change how you interact with your tenant for administration functions and don't prevent the existing tenant domain from being accessed. The Self Service Portal is available to selected cloud environments. [Contact us](http://mattr.global/contact) if you're interested in accessing these features or learning more. ## End to end OID4VCI support in the Self Service Portal 2024-05-06 Tags: Platform Management, Issuance The MATTR [Self Service Portal](/docs/platform-management/portal) now supports configuring your [OpenID for Verifiable Credentials Issuance (OID4VCI)](/docs/issuance/oid4vci-overview) workflow: * Creating an [Authentication Provider](/docs/issuance/authorization-code/authentication-provider/overview) configuration. * Configuring an [Interaction hook](/docs/issuance/authorization-code/interaction-hook/overview). * Configuring [Claims sources](/docs/issuance/claims-source/overview). * Creating Compact, Compact Semantic, Web and Mobile [Credential configurations](/docs/issuance/credential-configuration/overview). * Creating and sharing [Credential offers](/docs/issuance/credential-offer/overview). The Self Service Portal is available to selected cloud environments. [Contact us](http://mattr.global/contact) if you're interested in accessing these features or learning more. ## Credential issuance enhancements 2024-04-15 Tags: Issuance We have introduced new capabilities to support more issuance use cases, as well as improve integration capabilities. ### Key Features [#key-features] * You can now add an `issuanceDate` parameter to a signed Web Credential. This means the issued credential will only become valid once `issuanceDate` is in the past. This is only available for direct issuance of JSON credentials, and not via the OID4VCI workflow. * You can now pass objects and arrays as request parameters when [configuring a Claims source](/docs/issuance/claims-source/overview) for your [OID4VCI](/docs/issuance/oid4vci-overview) workflow. This means you can simplify integration with your existing data sources. ## Introducing Enhanced Ecosystems 2024-01-24 Tags: Trust Networks [Ecosystems](/docs/digital-trust-service) are part of the MATTR VII platform. They enable service providers to define policies regarding valid issuers, credential types and verifiers. This release introduces the following MATTR VII ecosystem capabilities: * Create an ecosystem: Create an ecosystem to act as the overarching entity that would include all the other components. * Create valid participants: Once an ecosystem is in place, you can create valid participants that can be trusted within it. Participants can be issuers and/or verifiers. * Configure valid credential types: Configure what credential types are valid in the ecosystem. * Create a policy: Create policies that define what participants are allowed to issue/verify what credentials in the ecosystem. * Retrieve a policy: Retrieve the ecosystem policies and use the information within them to apply your own business logic. Refer to our [Docs](/docs/digital-trust-service) to learn more about Ecosystems. ## Introducing Mobile Credentials 2023-11-08 Tags: Issuance We are thrilled to expand our credential profile suite by introducing support for [Mobile Credentials](/docs/concepts/mdocs). These are digital identity documents that are designed to be stored on the holder’s mobile devices. They offer a range of unique capabilities, making them an ideal choice for use cases which require higher assurance identity credentials, such as driving licenses or national IDs. Mobile Credentials are a MATTR VII implementation of the [ISO 18013-5:2021 specification](https://www.iso.org/standard/69084.html), created to standardize Mobile Driver Licenses (mDLs). When added to the digital trust ecosystem, Mobile Credentials can be applied to a wider variety of use cases and business problems. Our APIs offer the following features to support Mobile Credentials: * Signing Mobile Credentials and issuing them to holders. * Verifying Mobile Credentials using our Verifier SDKs. * Managing [Issuing Authority Certificate Authorities (IACAs)](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) and [Document Signer Certificates (DSCs)](/docs/issuance/certificates/overview#document-signer-certificate-dsc). Refer to our Docs to learn more about [Mobile Credentials](/docs/concepts/mdocs) and how they can be embedded into your digital trust ecosystem. ## Automatic Revocation when deleting credential data 2023-10-26 Tags: Issuance We’ve made a change to our credential deletion process so that any revocable credential will be automatically revoked when it is deleted from the MATTR VII Credential Registry. This creates an easier way to revoke and delete credentials by merging the two API requests into one, and also prevents deleting the credential data and losing Credential ID needed to revoke it later. Note that this change only applies to issued credentials that were configured to be revocable, and you can still update the revocation status for credentials without deleting them using the existing Revocation endpoints: * Update Revocation Status for a Web Credential * [Update Revocation Status for a Compact Credential](/docs/issuance/revocation/api-reference/cwt-status-update#update-cwt-credential-status) ## Removed support for DID:ION 2023-10-25 Tags: Maintenance From 25 October onwards, the `did:ion` method is no longer supported by the MATTR VII platform. This is the result of 3rd party providers no longer reliably supporting the required blockchain utilized by this method. This method was a trial capability. We apologize for any inconvenience and we welcome you to [reach out](http://mattr.global/contact-us) to us and discuss alternative supported DID methods such as `did:web` and `did:key`. ## Enhancements to Claims Sources Configuration 2023-09-05 Tags: Issuance We are excited to announce a suite of enhancements to [Claims sources](/docs/issuance/claims-source/overview) configuration in MATTR VII. These enhancements enable customers to better fine tune how MATTR VII interacts with their claims source: * Choose your preferred query method: You can now use both `POST` and `GET` when querying the claims source. = Choose your preferred authentication method: You can now authenticate with your claims source using either an API Key or your OAuth client credentials. * Query what you need: You can now query the claims source using a credential configuration as a query parameter. Refer to the Docs to learn more about [Claims sources](/docs/issuance/claims-source/overview). ## New Features and Enhancements across MATTR platforms 2023-08-03 Tags: Issuance, Holding We are happy to announce several key improvements across our MATTR platforms, highlighted by new self-service capabilities, enhanced APIs, and several MATTR Wallet features: ### Self-service Tenant Management [#self-service-tenant-management] Our new suite of API endpoints support the ability to manage tenants (create, view and delete) and analytics events in your environment. Please [contact us](mailto:dev-support@mattr.global) for more information and/or access to this new capability. ### Preventing Issuance of Expired Credentials [#preventing-issuance-of-expired-credentials] Our MATTR VII credential issuance API endpoints now prevent issuing any expired credentials where the current date has passed the specified expiry date. ### MATTR Wallet and MATTR GO Release [#mattr-wallet-and-mattr-go-release] Our recent MATTR Wallet release (V2.6.1) introduces support for claiming and storing Compact Credentials issued through the OID4VCI protocol. In addition, it includes several UI enhancements to further improve the wallet user experience. **MATTR GO Wallet** customers will receive a new release which includes all new features and enhancements from our latest MATTR Wallet version. ## Enhancements and new features across MATTR platforms 2023-07-07 Tags: Issuance, Holding We're excited to announce the following updates and new features across our MATTR platforms: * You can now issue Compact Credentials using the OpenID Credential Provisioning flow. This includes using all the features that this flow enables, such as integration hooks, claims source integration and multi-credential issuance for Compact Credentials. * We've enabled the MATTR Pi Wallet Toolkit with the capability to retrieve and hold Compact Credentials. * We now support multiple key types within a single DID, which means you can issue credentials in multiple Credential Profiles using the same DID. ## Enrichment of verification responses 2023-05-10 Tags: Verification We've released an enhancement to the way our [verification](/docs/verification) capabilities return the verified credential information. Until now, MATTR VII has applied a layer of convenience for integrations by returning only the claims from the verified credential. You can now also get the raw credential presentation shared by the holder in the verification response. This brings more information and options to verifiers that enable subsequent flows like: * Re-verifying a credential using the MATTR VII verify capabilities, to re-check things like the [revocation](/docs/issuance/revocation/overview) status. * Obtaining more information about which credential data attributes are coming from which credential when the verifier requested more than one credential. Check out our Docs for more details on how to enable and use this enhanced capability in your [credential verification flows](/docs/verification). ## Terms 2022-11-10 Tags: Platform Management We have an updated [Privacy Policy](/docs/resources/terms/privacy-policy) now in place. ## Introducing webhooks 2022-10-25 Tags: Platform Management, Verification We have added support for [Webhooks](/docs/platform-management/webhooks-overview) to MATTR VII. This new capability allows users to obtain information that is generated during an API operation that isn't otherwise available as part of the request or response payloads. Users are able to subscribe to specific events that are triggered on set MATTR VII operations. When an event is triggered, the information relating to that event is published via the webhook through to the URL(s) set up on the configured subscription(s). Users can now: * Create a webhook that is triggered on supported event types. * Verify a webhook to check the integrity and authorship of webhooks generated by the MATTR VII platform. Interested in learning more about how you might use the MATTR VII Platform? [Get in touch](http://mattr.global/contact-us) with us today. ## MATTR VII - Event logs 2022-08-15 Tags: Platform Management MATTR introduces enhanced platform ops logging levels on MATTR VII. As of today, we support configuration of logging at the platform environment level along with manual consumption of platform events in specific customer environments. In future customers will be able to customize these levels more freely and 'fan-out' events to other operational systems via APIs and webhooks. New logging levels supported: * Level 1 - Basic fields * Level 2 - Metadata + basic fields * Level 3 - Data (full request and response payloads) + metadata + basic fields All MATTR VII public cloud environments (and associated tenants) are set to Level 1 - Basic Fields. No personal identifiable information (PII) is being captured in event logs at this level. Interested in learning more about how you might use the MATTR VII Platform? [Get in touch](http://mattr.global/contact-us) with us today. ## OIDC Bridge - Additional configurations 2022-08-10 Tags: Issuance In this release, we added support for including the following configurations when setting up an OIDC Credential Issuer. * **federatedProvider.claimSource** is either `idToken` (default) `or` userInfo * **federatedProvider.tokenEndpointAuthMethod** is either `client_secret_post` (default), or `client_secret_basic` * **staticRequestParameters**: parameters that should be included in the request to the IDP. i.e. `display`, `prompt`, `max_age`, `ui_locales` etc. * **forwardedRequestParameters**: parameters that can be provided by the client to be forwarded to the IDP. These are optional and can override the staticRequestParameters i.e. `login_hint`. We've also updated our [MATTR Wallet SDK](/docs/holding/sdk-overview) and [MATTR Wallet App](/docs/holding/go-hold/getting-started) to include `login_hint` as a request parameter when issuing a credential using the OIDC bridge. This will allow pre-population of the username in the Federated Provider's login screen when using MATTR Wallet to claim a credential. Any other request parameters are not supported by MATTR Wallet and SDK at the moment. Interested in learning more about how you might use the MATTR VII Platform? [Get in touch](http://mattr.global/contact-us) with us today. ## New MATTR VII Regions 2022-08-01 Tags: Platform Management MATTR VII is now available in two additional AWS regions: * Frankfurt, Germany * Montréal, Canada. ## Introducing compact credentials 2022-06-30 Tags: Issuance Claims of data can be represented as [Compact Credentials](/docs/concepts/cwt), which are both cryptographically proven as authentic and dense enough to fit inside a QR code. This credential format is ideal where high information assurance is required but not high identity assurance about the entity presenting the credential. You can choose to use either a W3C Verifiable Credential data model to provide more descriptive semantic meaning or a more concise, non-semantic data model. The choices between the data model to use comes down to how compact you need the credential to be versus how openly you intend to share and exchange the created credentials across different domains and jurisdictions. The following capabilities of Compact Credentials are provided in this MATTR VII platform release: * Sign and issue a Compact Credential in the semantic model or compact model * Verify a Compact Credential. * [Revoke](/docs/issuance/revocation/overview) a Compact Credential. * [Format the Compact Credential](/docs/issuance/cwt-direct-issuance#format-the-signed-cwt-credential) in a way that allows it to be presented in either a digital or paper-based manner. With this product release, our [Customer Agreement and Terms](/docs/resources/terms/customer-agreement) have changed. Please refer to the version dated 30 June 2022 for details. ## Compressed credential support (technical preview) 2022-03-24 Tags: Issuance Compressing semantically verifiable credentials into smaller payload sizes is a useful technique. For example, it allows credentials and presentations to be embedded into QR codes so they can be used when one party is offline. Try out our latest technical preview on compressed credentials to see how using CBOR-LD can unlock use cases where offline is important. Convert JSON-LD to CBOR-LD to compress the payload size Use the latest version of the [MATTR Wallet](/docs/holding/go-hold/getting-started) (v1.9.1) to present applicable credentials in a CBOR-LD format Convert CBOR-LD payloads to JSON-LD to use with existing MATTR VII API ## Digital Covid Certificate (DCC) Extension 2021-11-05 Tags: Issuance We have introduced a new DCC extension to MATTR VII that is built on top of our core libraries to provide the capability to issue and verify Digital Covid Certificates (DCC). The standards outlined for the European Union DCC (EUDCC) format, which covers 3 certificate types (vaccination, recovery, and testing) are all covered by the extension which allows your MATTR VII tenant the ability to: * Maintain the required document signer certificates that facilitate trusted issuance and verification of the EUDCC format. * Sign and issue a health certificate payload into a EUDCC format * Verify a EUDCC * Format the EUDCC in a way that allows it to be presented in either a digital or paper-based manner. The use of the DCC extension during a trial of the MATTR VII platform may be subject to change. As you move into production workloads please get in touch to discuss your needs. ## Introducing the New Zealand COVID Pass (NZCP) Verify Extension 2021-11-05 Tags: Issuance, Verification The New Zealand government will start issuing a type of digital health certificate known as a 'My Vaccine Pass' using the [New Zealand COVID Pass (NZCP) specification](https://github.com/minhealthnz/nzcovidpass-spec) , this credential contains a limited set of personal information and provides a way for the holder to prove they meet certain health policy requirements in regards to COVID-19 such as being vaccinated against the virus. From today you can now read about the NZCP Verifier API to help you determine how to integrate and verify NZ COVID Passes that have been presented to you, this also accompanies the NZCP Verifier SDK and Verifier white label app offerings. [Get in touch](http://mattr.global/contact-us) to start onboarding to use the service today, the API will also be available on a trial basis starting soon. ## ZKP-enabled credentials using Web DIDs & support for custom paths 2021-10-22 Tags: Issuance This release adds the ability to use `bls12381g2` key types with a [Web DID](/docs/concepts/dids#didweb) so that ZKP-enabled credentials can be issued. We have also enabled Web DIDs to be created on custom paths that don't rely on a /.well-known location. * Create a DID with the web method and a `bls12381g2` key type. * A new `url` parameter in `options` to specify a domain for the Web DID as well as allowing the use of paths in the form of `organization.com/path`. The `domain` options parameter has now been functionally superseded by the `url` option parameter and will be deprecated in an upcoming release. ## Introducing ION DIDs & an update on Sovrin DIDs 2021-09-08 Tags: Issuance, Verification Decentralized Identifiers (DIDs) using the [Identity Overlay Network (ION)](https://identity.foundation/ion/) method can now be created on the platform and used for issuing credentials and other purposes. ION DIDs use the Sidetree protocol to anchor the DID document to a ledger, which provides a high-throughput and efficient method for writing to a blockchain like Bitcoin. ION DIDs can be easily configured on the MATTR VII platform using our API interface, allowing you to leverage the benefits without having to deal with any of the underlying complexities: * Create & manage ION DIDs on your tenant * ION DIDs can be used to create credentials, sign and encrypt messages as well as being fully configurable on the OIDC Bridge for issuance and verify * Supports `ed25519` and `bls12381g2` key types * Fully resolve ION DID Documents from the public nodes The creation of ION DIDs during a trial of the MATTR VII platform may be subject to change. As you move into production workloads please get in touch to discuss your needs. ### Sovrin DID method [#sovrin-did-method] Since launching the platform our implementation of `did:sov` has relied on private Indy nodes whilst the community around DID Sovrin continued to develop new kinds of interoperability in their infrastructure. Recently activity is showing that rather than converging around the Sovrin-specific method that’s been used to date new approaches are being looked at. Until this direction from the community has more clarity around implementation we have decided to deprecate our current private node support. From this release, we will begin phasing out support for DIDs based on Sovrin by removing references from the documentation and in the next release, we will stop the current `did:sov` support and remove any Sovrin DIDs from the sandbox platform. ## Custom domains & complex credentials 2021-07-02 Tags: Platform Management, Verification, Holding Tenants can now be configured to represent as a verified custom domain: * [Custom domains](/docs/platform-management/custom-domain-overview) are a paid feature, setting up a custom domain whilst using a sandbox is possible, however, note this may be disabled and reverted back at MATTR's discretion. * [New endpoints](/docs/platform-management/custom-domain-api-reference#create-a-custom-domain) added to create, view, delete and verify a custom domain on your tenant * [Create a custom domain](/docs/platform-management/custom-domain-guide) by providing details like your organization name, domain and a logo which will be displayed to end-users interacting with your tenant using a wallet app that supports a web manifest payload. * The [MATTR mobile wallet](/docs/holding/go-hold/getting-started) app has been updated to support the display of custom domains as well as a number of improvements to the UI of MATTR Wallet to be more human-friendly, including support for more complex data types like nested data and embedded images. ## Create & Verify Presentations Directly 2021-05-20 Tags: Verification New endpoints provided to help you work with verifiable presentations directly on MATTR VII: * [Verify a presentation](/docs/verification) obtained from any source adhering to the [W3C Verifiable Credential Data Model](https://www.w3.org/TR/vc-data-model/#presentations). * Create verifiable presentations using Credentials where the subject(s) are controlled by the tenant This is a useful operation for exploring how verifiable presentations are created and can be submitted to the Verify a presentation endpoint. An optional `description` parameter has been added when creating credentials: * The optional `description` field is enabled on the Create Credential endpoint. * The field can be configured in the OIDC Bridge Issuer so that any credentials issued will contain the description. The description field is part of the W3C Verifiable Credential Model v2 specification and will be supported in the MATTR mobile wallet as the standard begins to stabilize. ## MATTR VII launch with push notification messaging 2021-03-25 Tags: Issuance MATTR VII is now live! ### Pricing [#pricing] Pay-as-you-go pricing is now published * Get a detailed look at how MATTR VII is charged once you elect to upgrade to a paid plan. * To discuss high-volume discounts, please [contact us](http://mattr.global/contact-us). ### API references [#api-references] The platform is now known as MATTR VII; URLs and paths updated to reflect this: * MATTR VII Core is `https://tenant.vii.mattr.global/core/v1`. * OIDC Bridge is a MATTR VII extension found at `https://tenant.vii.mattr.global/ext/oidc/v1`. Old domains and paths will be discontinued from service within 30 days. ### Notification messaging [#notification-messaging] Customers can use their tenant to construct and send messages to holders based on their subject DID, which will be delivered to the MATTR Wallet app and notified via a push notification. * Construct action-based messages in a DIDComm2 JWM format: * Start a credential issuance using the OIDC Bridge. * Notify of a credential revocation status change. * Start a verification flow using a callback. * Encrypt messages intended for the recipient. * MATTR VII enforces end-2-end encryption (E2EE), so message contents are never visible to MATTR when held in messaging inboxes. * Route messages to a dedicated inbox for the wallet user. The MATTR Wallet app is being updated to support receiving push notification and managing messaging inboxes. Make sure you update to the latest version available on the [App Store](https://apps.apple.com/us/app/mattr-wallet/id1518660243) or [Google Play](https://play.google.com/store/apps/details?id=global.mattr.wallet). Further messaging capabilities are scheduled on the roadmap. ### Terms [#terms] New customers signing up to MATTR VII will have a new customer agreement, SLA and privacy policy in place. ## Verify ZKP-enabled Credentials 2021-02-18 Tags: Issuance, Verification Further functionality to support the use of privacy-preserving credentials using BBS+ signatures. ### Create a JSON-LD Frame Presentation Request [#create-a-json-ld-frame-presentation-request] * Use a query extension to the Verifiable Presentation Request Specification format, Query by Frame, to specify required credential claims. * Trusted Issuers and Credential Types are used to match credentials in the mobile wallet. ### Mobile Wallet updates [#mobile-wallet-updates] * The latest version (v0.50.0) of the Mobile Wallet is required to process Query by Frame presentation requests. * ZKP-enabled credentials using BBS+ signatures can be used to derive selectively disclosed presentations. * New UI screens to actively show the disclosure of claims. ## Maintenance 2021-02-11 Tags: Issuance Maintenance Release * Update to the Callback URL for all Issuers on the OIDC Bridge to align with future changes. Ensure that the allowed callback URL for your federated provided is updated with the new path. From `../oidc/v1/issuers/..` To: `../ext/oidc/v1/issuers/..`. ## OIDC Bridge and OIDC Credential Provider 2020-12-16 Tags: Issuance When we first launched the Platform we pioneered the bridging of existing identity solutions using Open ID Connect (OIDC) to a new world of decentralized identity and verifiable credentials. During this time we listened to customers as well as working within the Community as standards evolve. This latest version of the OIDC Bridge is now easier to set up, more flexible to integrate and conforms with [OIDC Credential Provider](https://mattrglobal.github.io/oidc-client-bound-assertions-spec/) for issuing credentials to the mobile wallet. ### OIDC Bridge [#oidc-bridge] * Multiple OIDC Credential Issuers can be enabled to offer credentials using the OIDC Configuration metadata endpoints * Custom `scopes` can be added to Federated Providers to enable more flexibility in obtaining ID token claims * OIDC Credential Verifiers are easier to set up and associated OIDC Clients can be listed and updated * Authenticate a DID using OIDC Bridge introduces a new way for OIDC Clients to obtain a Subject identifier that has been verified to come from the holder. * Claim mappings: OIDC claims > JSON-LD terms and JSON-LD terms > OIDC claims have been revamped to simplify their use and make it clearer on how they are used by the OIDC Bridge ## Maintenance 2020-11-17 Tags: Issuance, Verification Maintenance release In line with the [W3C VC Data Model](https://www.w3.org/TR/vc-data-model/); Subject identifiers are now not required on Create Credential, usually a Subject DID makes up a core part of a Verifiable Credential but in some cases it makes sense without one, such as issuing a ‘bearer’ style credential e.g. a concert ticket or when the credential is to be stored on behalf of a subject and reissued later with subject binding. ## Maintenance 2020-11-04 Tags: Holding Maintenance release * The format of the response from [`/.well-known/did-configuration`](/docs/api-reference/platform/dids/wellKnownDidConfig) is now in a JSON-LD format. Learn more about the [Well Known DID Configuration](https://identity.foundation/.well-known/resources/did-configuration/) from the Decentralized Identity Foundation working group. * This changes means all holders will need to being using the MATTR Mobile Wallet with a minimum version of v0.37.1 to continue to receive and present credentials, earlier versions of the app will present a generic error message. ## Revocable Credentials 2020-10-21 Tags: Issuance Credentials issued on the platform are now revocable and searches can be performed on the Credential Registry. ### Revocable Credentials [#revocable-credentials] * Create Credential has new optional revocable property to create a Credential as revocable using a revocation list method. * All Credentials issued using the OIDC Bridge are now revocable by default. * API endpoints for an Issuer to toggle the revoke status of a Credential. * Provisioned hosting of revocation lists for Credential Issuers. * Automatic verification of a presented Credential against its revocation list will result in revoked credentials being returned with an error message in the OIDC/OAuth2 callback response back to Verifiers/relying parties. ### Search on Credentials [#search-on-credentials] * Credentials optionally held in the Credential Registry can now be retrieved by `tag` and `type` parameters. * The meta-data of non-persisted Credentials can also be found using these tags. * All Credentials issued using the OIDC Bridge will only store meta-data. ### Updates [#updates] Pagination on [Retrieve List of DIDs](/docs/api-reference/platform/dids/retrieveListOfDids) and Retrieve List of Presentation Templates now has pagination using the cursor-based method. ## DID Web Method 2020-10-07 Tags: Issuance New [DID](/docs/concepts/dids) method [`did:web`](/docs/concepts/dids#didweb) is available to be created on the Platform. * Further content on the various DID methods available on the platform is available. ### Updates [#updates] Enhanced pagination on the List Credentials endpoint, moved from using a page-offset pagination to a much more performant cursor-based pagination. ## ZKP-Enabled Credentials 2020-09-16 Tags: Issuance Support added for issuing privacy-preserving credentials using BBS+ signatures. ZKP-enabled credential functionality during Preview are considered experimental and may change over time as well as any ZKP-enabled credentials issued during this period may need to be reissued. ### Create a DID with BLS Key Type [#create-a-did-with-bls-key-type] * [Create DID](/docs/api-reference/platform/dids/createDid) now has options to set a key type (only for `did:key` method at this time). * Use the BLS key type `bls12381G2` to create Issuer DIDs for issuing ZKP-enabled credentials. * Response for [Resolve a DID](/docs/api-reference/platform/dids/resolveDid) has been altered to include a `localMetadata` parameter which will be used for future DID methods. ### Create a ZKP-enabled Credential [#create-a-zkp-enabled-credential] Create Credential will automatically issue ZKP-enabled credentials if an `issuerDid` referencing a `bls12381G2` key type is provided. ### Updates [#updates] New optional parameters are available on Create Credential: * Providing a value in `tag` will set this value as metadata so it can be referenced on the platform later. * Setting the `persist` boolean to `true` will store the created credential on your tenant for future retrieval. The default value is not to store credentials. ## Maintenance 2020-09-09 Tags: Holding Maintenance release. * Mobile Wallet App bug fixes and improvements. * Improved support for OIDC query parameters on mobile app authorization requests. ## Sovrin DID Method 2020-08-25 Tags: Issuance Creation of Sov DIDs on the platform is now possible. * [Create DID](/docs/api-reference/platform/dids/createDid) can be used to create DIDs using the Sovrin DID method. Note during Preview these will not be anchored on the Sovrin MainNet. * [Resolve a DID](/docs/api-reference/platform/dids/resolveDid) will resolve Sov DIDs including MATTR issued ones. ## Maintenance 2020-08-10 Tags: Verification Maintenance release Tidy up of error response messages on Create Presentation Templates and messaging endpoints. ## Maintenance 2020-07-29 Tags: Maintenance New endpoints available. * Update operations now possible using Update a Claim Mapping and Update a Provider. ## Launch of the MATTR Preview Platform 2020-06-03 Tags: Issuance, Verification, Trust Networks, Holding ### SaaS Platform [#saas-platform] * A cloud-hosted, multi-tenanted environment that can be spun-up on-demand using managed containers * Authentication and access-control provisioning * Auditing and privacy-preserving logging ### Issue Verifiable Credentials using OpenID Connect [#issue-verifiable-credentials-using-openid-connect] * Cryptographically secure issuance of Verifiable Credentials (VC) to authenticated identity holders * Configuration options to; * Bring-your-own OpenID Connect Provider (OP) * Or, use our step-by-step tutorial for a reference OP * Map personal information claims from source to VC terms, using linked-data standards * Decode a JWT signed using a Decentralized Identifier * Optionally; store issued credentials on-platform to be retrieved (for non-sensitive use-cases) * Create a credential Offer as a QR code or deep-link to start the issuance flow with the mobile wallet app * The static offer is ready to display on a website or physical media e.g. a bus shelter advertisement ### Verify Verifiable Credentials using OpenID Connect [#verify-verifiable-credentials-using-openid-connect] * Cryptographically secure verification of VCs from identity holders after their consent * Uses the latest standards-based messaging protocols (JWM) to transmit information from the holder * Configure an OpenID Connect Relying Party client to accept holder information via a standard ID token * Map personal information from Credential claims to a standard ID token * Create a VC Request and embed using a QR code or deep-link into a journey * The dynamic request can be used in an information-gathering flow e.g. Customer onboarding ### Mobile Wallet App [#mobile-wallet-app] * Native iOS and Android apps, supporting a range of models and devices * On-device biometric access enabled * Familiar chat-like user-interface approach, designed with core pillars of privacy, accessibility and user-experience * Puts the user in control during issuance and verification of their Credentials * Keeps user in-context with in-app-browser technology * Interoperable to published specifications within the Self-Sovereign Identity ecosystem * Theming options available to prospective customers ## DID Web hosting now on MATTR VII Tags: Issuance To help customers get started with using verifiable credentials quickly and easily, we now support [DID Web hosting](/docs/concepts/dids#didweb) on the MATTR VII platform. ## Enhanced issuance journeys with OpenID Credential Provisioning Tags: Issuance We're thrilled to unveil the evolution of our credential issuance capabilities with the all-new [OpenID Credential Provisioning flow](/docs/issuance/oid4vci-overview), based on the [OpenID for Verifiable Credential Issuance (OID4VCI)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) protocol. This protocol is a key draft standard for interoperability among digital wallets and has been included in the eIDAS expert group's draft [European Digital Identity Architecture and Reference Framework (EUDI ARF)](https://mattr.global/resources/articles/demystifying-the-eudi-arf-part-one) for digital wallets. The new flow has evolved from our original OIDC Bridge credential issuance capabilities based on market and community movements and feedback from customers. It simplifies the experience of generating and configuring a credential for the issuer and it enhances the user experience of collecting a credential. ## Enhancements and new features in the MATTR universe: April 2023 Tags: Issuance, Verification, Holding The MATTR team has been busy the last few months! We have a raft of exciting new features and updates coming to the MATTR platforms in April 2023. * Next-generation credential issuance with our new OpenID Credential Provisioning flow, using the OID4VCI standard. * More flexibility for credential issuance with interaction hooks and claims source integration. * DID Web hosting on the MATTR VII platform to simplify onboarding. * Major changes to our MATTR VII API with a version 2 release. * An update to the MATTR Wallet and Pi Wallet Toolkit to support an improved approach of matching credentials to presentation requests from verifiers. ## New major changes to MATTR VII API Tags: Maintenance Continuing our theme of simplicity and ease of use, we will be releasing a new major version of our API, which includes a new set of endpoints that simplifies the ability to utilize MATTR’s [Credential Profiles](/docs/concepts/formats-overview). Credential Profiles combine data about people, organizations or things with unique digital signatures. We use different types of Credential Profiles depending on the type of information a customer wants to convey and how they want to convey it. See the \[API reference] for more information on these changes. ## Tools for extra flexibility in credential issuance Tags: Issuance Our \[OpenID Credential Provisioning flow]\([OpenID Credential Provisioning flow](/docs/issuance/oid4vci-overview)) makes issuance easier than ever before and we have built extra features that enable customers to have more flexibility to enact their unique business logic into the flow. These include: * **Interaction hooks**: integrate additional steps to the credential claiming journey such as additional biometric checks, identity assurance flows, or informational screens. * **Multi-credential issuance**: Issue multiple credentials to a wallet holder within a single user journey. * **Claims source integration**: Configure credentials using data from an existing source and supplement with additional data from tenant-managed user claims as well as claims sourced from an authentication provider or IDP. More tools on the way soon! For current customers, we will continue to support the OIDC Bridge for issuance through the end of 2023 to allow you to transition to the new protocol and feature set. ## Verify Credentials without using OIDC Bridge Tags: Issuance, Verification * Unlocks Verifying a Credential using a Callback method to allow non-OIDC verification * Introduction of a new endpoint to Verify a Credential directly using the API # Supported standards URL: /docs/resources/standards/supported This page outlines the currently-implemented support of MATTR products and services for various open standards. If there is a standard (listed here or not) for which you would like further clarification with respect to product support, please [contact us](http://mattr.global/contact-us). ## Standards authorities [#standards-authorities] The following is a list of globally-recognized standards development organizations (SDO's) where we help develop and participate in standardization efforts and implement standards: * [Decentralized Identity Foundation (DIF)](https://identity.foundation/) * [OpenID Foundation (OIDF)](https://openid.net/foundation/) * [World Wide Web Consortium (W3C)](https://www.w3.org/) * [Internet Engineering Task Force (IETF)](https://www.ietf.org/) * [International Organization for Standardization (ISO)](https://www.iso.org/home.html) ## Supported standards status [#supported-standards-status] With respect to each standard, the status indicates the current and likely continued future conformance of relevant MATTR products and services to the particular version of the standard indicated. The status is influenced by the product roadmap, future iterations of the standard itself, the changing standards landscape, and other such factors. Support statuses are categorized as either: * **Stable** - MATTR products and services conform to the standard and conformance is expected to continue into the foreseeable future. Deprecation of support for the standard and/or adoption of a new version is unlikely over any reasonable time horizon. * **Provisionary** - MATTR products and services conform to the standard however deprecation of support for the standard and/or adoption of a new version is possible within the foreseeable future. * **Deprecated** - MATTR products and services previously conformed to the standard, the support for which has now ceased. ### Core standards supported [#core-standards-supported] | Standards organization | Standard | Version | Status | | :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------- | :----------- | | W3C | [Decentralized Identifiers (DIDs)](https://www.w3.org/TR/did-core/ "Decentralized Identifiers (DIDs)") | 1.0 | Stable | | W3C | [Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/ "Verifiable Credentials Data Model") | 1.1 | Stable | | W3C | [JSON-LD](https://www.w3.org/TR/json-ld/ "JSON-LD") | 1.1 | Stable | | W3C CCG | [Revocation List 2020](https://w3c-ccg.github.io/vc-status-rl-2020/ "Revocation List 2020") | Draft Community Group Report 20 April 2021 | Provisionary | | W3C CCG | [Verifiable Presentation Request Specification](https://w3c-ccg.github.io/vp-request-spec/ "Verifiable Presentation Request Specification") | 0.7 Draft Community Group Report 26 September 2022 | Provisionary | | W3C/WICG | [Digital Credentials API](https://github.com/w3c-fedid/digital-credentials) | WD | Provisionary | | DIF | [DIDComm Messaging](https://identity.foundation/didcomm-messaging/spec/ "DIDComm Messaging") | 1.0 | Provisionary | | DIF | [Well Known DID Configuration](https://identity.foundation/.well-known/resources/did-configuration/ "Well Known DID Configuration") | DIF Working Group Approved Draft | Stable | | IETF | [JSON Web Encryption (JWE)](https://datatracker.ietf.org/doc/rfc7516/) | 1.0 | Stable | | IETF | [JSON Web Key (JWK)](https://datatracker.ietf.org/doc/rfc7517/) | 1.0 | Stable | | IETF | [JSON Web Signature (JWS)](https://datatracker.ietf.org/doc/rfc7515/) | 1.0 | Stable | | IETF | [HTTP Signatures](https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/) | Proposed standard | Stable | | IETF | [Token Status List](https://drafts.oauth.net/draft-ietf-oauth-status-list/draft-ietf-oauth-status-list.html) | Draft 14 | Stable | | OIDF | [OpenID Connect](https://openid.net/connect/ "OpenID Connect") | 1.0 | Stable | | OIDF | [OpenID4VCI](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) | 1.0 | Stable | | OIDF | [OpenID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) | ID-2 | Provisionary | | ISO/IEC | [18013-5 - Mobile Driving License (mDL)](https://www.iso.org/standard/69084.html) | 2021 | Stable | | ISO/IEC | [18013-7 - Mobile Driving License (mDL) add-on functions](https://www.iso.org/standard/91154.html) | 2024 | Stable | | ISO/IEC | [23220-4 - Identity management via mobile devices - protocols and services for operational phase](https://www.iso.org/standard/86785.html) | Final DTS | Provisionary | | ISO/IEC | [23220-3 - Identity management via mobile devices - protocols and services for issuing phase](https://www.iso.org/standard/86783.html) | WD14 | Provisionary | ### DID methods supported [#did-methods-supported] | Standards organization | Standard | Version | Status | | :--------------------- | :--------------------------------------------------- | :------------------------------------- | :----------- | | W3C | [DID Key](https://w3c-ccg.github.io/did-key-spec/) | 0.7 Unofficial Draft 02 September 2022 | Provisionary | | W3C | [DID Web](https://w3c-ccg.github.io/did-method-web/) | 30 January 2023 | Provisionary | ### Cryptographic suites supported [#cryptographic-suites-supported] | Standards organization | Standard | Version | Status | | :--------------------- | :-------------------------------------------------------------------------------------------- | :---------------------------------------- | :----------- | | IETF | [BBS Signatures Suite 2022](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bbs-signatures/) | Proposed standard | Provisionary | | W3C | BBS+ Signatures 2020 | Draft Community Group Report 16 Jan 2023 | Deprecated | | W3C | [Ed25519 Signature 2018](https://w3c-ccg.github.io/lds-ed25519-2018/) | Draft Community Group Report 23 July 2021 | Provisionary | # Upcoming standards URL: /docs/resources/standards/upcoming This page outlines the open standards that are actively being followed by MATTR with a view to potential placement on the product roadmap and eventual product support. If there is a standard (listed here or not) for which you would like further clarification with respect to product support, please [contact us](http://mattr.global/contact-us). ## Standards authorities [#standards-authorities] The following is a list of globally-recognized standards development organizations (SDO's) where we help develop and participate in standardization efforts and implement standards: * [Decentralized Identity Foundation (DIF)](https://identity.foundation/) * [OpenID Foundation (OIDF)](https://openid.net/foundation/) * [World Wide Web Consortium (W3C)](https://www.w3.org/) * [Internet Engineering Task Force (IETF)](https://www.ietf.org/) * [International Organization for Standardization (ISO)](https://www.iso.org/home.html) ## Standards [#standards] | Standards organization | Standard | Version | | :--------------------- | :--------------------------------------------------------------------------------------- | :------ | | IETF | [JSON Web Proof (JWP)](https://datatracker.ietf.org/doc/draft-ietf-jose-json-web-proof/) | -04 | # Terms and Conditions URL: /docs/resources/terms The terms governing your use of MATTR products and services. Our commitments regarding the availability and performance of our services. How we collect, use, and protect your personal information. Details on how we handle and process data on your behalf. A list of third-party sub-processors we engage to assist in providing our services. The terms governing your use of our website. The terms governing your use of our software development kits (SDKs). The terms governing your use of our MATTR GO whitelabel applications. # How to configure holder certificates URL: /docs/holding/certificates/guide Holder applications use [Wallet Attestation](/docs/holding/credential-claiming-guides/wallet-attestation) to prove their authenticity to credential issuers. This attestation is verified using a [chain of trust](/docs/holding/certificates/overview) model, where wallet attestation JWTs are linked to the holder application operator via a series of certificates. MATTR VII supports both managed and unmanaged (external) holder certificates, allowing you to choose how you manage your certificate infrastructure. * With **managed** holder certificates, you create a Holder root CA certificate and MATTR VII manages the rest. Wallet attestation signers (and their certificates) are automatically provisioned when needed to sign wallet attestation JWTs. * With **unmanaged** holder certificates, you manage the entire lifecycle. You generate the Holder root CA certificate, create wallet attestation signers, use the returned CSR to obtain signed certificates from your root CA, and upload them to MATTR VII. See [unmanaged certificates](/docs/holding/certificates/overview#unmanaged-external-certificates) for more details. ## Creating a Holder root CA certificate [#creating-a-holder-root-ca-certificate] ### Create a managed Holder root CA [#create-a-managed-holder-root-ca] Make a request of the following structure to [create a managed Holder root CA](/docs/api-reference/platform/holder-root-ca-certificates/createHolderCaCertificate): ```http filename:"Request" POST /v1/holder/certificates/ca ``` ```json filename:"Request body" { "commonName": "Example Holder root CA", "country": "NZ" } ``` * `commonName` : This *optional* parameter indicates the common name of the Holder root CA certificate. When specified, the value must be a valid `PrintableString` and cannot be an empty string. If not provided, the value defaults to `{tenantDomain} Wallet Attestation`. * `country` : This *optional* parameter indicates the holder country. When specified, the value must be a valid country code as per [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html). The response will include an `id` property, which is a unique identifier for the Holder root CA. This identifier is used in subsequent operations to reference this Holder root CA. The Holder root CA is always created as **inactive**. You will activate it in the next step. ### Activate the Holder root CA [#activate-the-holder-root-ca] Make a request of the following structure to [update the Holder root CA](/docs/api-reference/platform/holder-root-ca-certificates/updateHolderCaCertificate) and activate it: ```http filename:"Request" PUT /v1/holder/certificates/ca/{certificateId} ``` * `certificateId` : Replace with the `id` value obtained when you created the Holder root CA. ```json filename:"Request body" { "active": true } ``` Setting `active: true` deactivates any previously active Holder root CA for the tenant (only one can be active at a time). Once a managed Holder root CA is activated, MATTR VII automatically provisions wallet attestation signers on demand when the first wallet attestation request is made. No additional configuration is required. ### Generate a self-signed root certificate (Holder root CA) [#generate-a-self-signed-root-certificate-holder-root-ca] Use your preferred cryptographic library or tool to generate a self-signed root certificate (Holder root CA). This certificate will later be used to sign wallet attestation signer certificates. When using unmanaged (external) certificates, you assume full responsibility for the secure management of the root certificate and all subordinate certificates. This includes ensuring the protection, proper issuance, and timely revocation of certificates under the uploaded root, as MATTR VII does not manage or monitor these certificates on your behalf. ### Register the external Holder root CA certificate with MATTR VII [#register-the-external-holder-root-ca-certificate-with-mattr-vii] Make a request of the following structure to [create an unmanaged Holder root CA](/docs/api-reference/platform/holder-root-ca-certificates/createHolderCaCertificate): ```http filename:"Request" POST /v1/holder/certificates/ca ``` ```json filename:"Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICAzCCAaqgAwIBAgIJAKFOoPvy+rfbMAoGCCqGSM49BAMCMCUxCzAJBgNVBAYT\r\nAk5aMRYwFAYDVQQDDA1NQVRUUiBFeGFtcGxlMB4XDTI2MDUxNDAwMjEzNVoXDTM2\r\nMDUxMTAwMjEzNVowJTELMAkGA1UEBhMCTloxFjAUBgNVBAMMDU1BVFRSIEV4YW1w\r\nbGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATTSjjTuOouwLNObXEwm0TNNWu5\r\nQfuWW1aMaIz/SwzKfHEP8A4fAgiEYF5SfO5Fy+cQ8m159e3go7yAzmyE1zgIo4HC\r\nMIG/MBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQW\r\nBBQ8paHD6yC0HheUE53YCRx1fR/8JzB6BgNVHR8EczBxMG+gbaBrhmlodHRwczov\r\nL2V4YW1wbGUuY29tL3YyL2NyZWRlbnRpYWxzL2hvbGRlci93YWxsZXQtYXR0ZXN0\r\nYXRpb24vY2FzLzJlODljMTU2LTMxZDUtNDc4My1iZDU5LTkwNTViNWY4ZTdkMi9j\r\ncmwwCgYIKoZIzj0EAwIDRwAwRAIgJkH+qkQ4Rz//MBU61u3fDZOrHYejR2FIK1Lv\r\nIEiNRDACIHePIX2AEK0Ls6wIxgyJt4hdj64QSSzH1hJzMPGGFyM9\r\n-----END CERTIFICATE-----\r\n" } ``` * `certificatePem` : This required parameter contains the PEM-encoded Holder root CA certificate. The certificate must be valid and not expired. The response will include an `id` property, which is a unique identifier for the unmanaged Holder root CA. This identifier is used in subsequent operations to reference this Holder root CA. ### Create a wallet attestation signer [#create-a-wallet-attestation-signer] Make a request of the following structure to [create a wallet attestation signer](/docs/api-reference/platform/wallet-attestation-signers/createWalletAttestationSigner) that references the unmanaged Holder root CA: ```http filename:"Request" POST /v1/holder/certificates/wallet-attestation-signers ``` ```json filename:"Request body" { "caId": "080c670a-2e90-4023-b79f-b706e55e9bc6" } ``` * `caId` : Replace with the `id` value obtained when you created the unmanaged Holder root CA in the previous step. Attempts to provide a managed Holder root CA identifier for manual wallet attestation signer creation will result in an error. The response will include two properties which you will use later in this guide: * `id` : The unique identifier for the wallet attestation signer. This identifier is used in subsequent operations to reference this signer. * `csrPem` : The X.509 Certificate Signing Request (CSR) in PEM format. You will use this CSR to generate a valid wallet attestation signer certificate in the next step. ### Generate and sign the wallet attestation signer certificate [#generate-and-sign-the-wallet-attestation-signer-certificate] Use your preferred cryptographic library or tool to generate and sign a wallet attestation signer certificate using the CSR provided in the response from the previous step. The certificate must be signed by your Holder root CA. ### Associate the certificate with the wallet attestation signer [#associate-the-certificate-with-the-wallet-attestation-signer] Make a request of the following structure to [update the wallet attestation signer](/docs/api-reference/platform/wallet-attestation-signers/updateWalletAttestationSigner) to upload the signed certificate and activate the signer: ```http filename:"Request" PUT /v1/holder/certificates/wallet-attestation-signers/{certificateId} ``` * `certificateId` : Replace with the `id` value obtained when you created the wallet attestation signer in the previous step. ```json filename:"Request body" { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICRjCCAe2gAwIBAgIJAMaBeZ37qSVQMAoGCCqGSM49BAMCMCUxCzAJBgNVBAYT\r\nAk5aMRYwFAYDVQQDDA1NQVRUUiBFeGFtcGxlMB4XDTI2MDUxNDAwMjEzNVoXDTI3\r\nMDUxNDAwMjEzNVowTTELMAkGA1UEBhMCTloxPjA8BgNVBAMMNU1BVFRSIEV4YW1w\r\nbGUgV2FsbGV0IEF0dGVzdGF0aW9uIFNpZ25lciAxNzc4NzE2ODAwMDAwMFkwEwYH\r\nKoZIzj0CAQYIKoZIzj0DAQcDQgAE2bX0ZXV2DyFj3d1Va98s4PiBMN48ZFCf2dQZ\r\nl6Y/lIZfQQjQcIEWi+dKAsYyaqeUi81yxzvXfmRogVh9nweQoaOB3TCB2jAMBgNV\r\nHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIHgDAdBgNVHQ4EFgQUQ3gaPaANZ83EHYNE\r\nrgjFP8z3VtowHwYDVR0jBBgwFoAUPKWhw+sgtB4XlBOd2AkcdX0f/CcwegYDVR0f\r\nBHMwcTBvoG2ga4ZpaHR0cHM6Ly9leGFtcGxlLmNvbS92Mi9jcmVkZW50aWFscy9o\r\nb2xkZXIvd2FsbGV0LWF0dGVzdGF0aW9uL2Nhcy8yZTg5YzE1Ni0zMWQ1LTQ3ODMt\r\nYmQ1OS05MDU1YjVmOGU3ZDIvY3JsMAoGCCqGSM49BAMCA0cAMEQCICdeu9XvaCqW\r\nYwkST+wYtrm4awOt+mRQwqrMMVjAQj1BAiBgdrg0JeXyofGrZvb34TEM/gQDi/HV\r\nHCqUhEUm9T92pw==\r\n-----END CERTIFICATE-----\r\n" } ``` * `active` : Set to `true` to activate the signer. Can only be set to `true` when a `certificatePem` is provided. Only active wallet attestation signers can be used to sign wallet attestation JWTs. * `certificatePem` : The PEM-encoded certificate signed by your Holder root CA. This field is immutable after the first upload — subsequent PUT requests may only toggle `active`. ### Activate the Holder root CA [#activate-the-holder-root-ca-1] Make a request of the following structure to [update the Holder root CA](/docs/api-reference/platform/holder-root-ca-certificates/updateHolderCaCertificate) and activate it: ```http filename:"Request" PUT /v1/holder/certificates/ca/{certificateId} ``` * `certificateId` : Replace with the `id` value obtained when you registered the unmanaged Holder root CA. ```json filename:"Request body" { "active": true } ``` ### Wallet Attestation is ready [#wallet-attestation-is-ready] Once the Holder root CA and wallet attestation signer are activated, they are used to sign wallet attestation JWTs. When a holder application with [SDK Tethering](/docs/holding/sdk-operations/sdk-tethering) configured requests a Wallet Attestation token, the tethered MATTR VII automatically selects a valid and active wallet attestation signer to sign the JWT. If there is no valid and active wallet attestation signer, MATTR VII will return an error. Unlike the managed flow, MATTR VII does not automatically create new wallet attestation signers in the unmanaged flow, and you are responsible for manually creating and uploading them as needed. # Overview URL: /docs/holding/certificates/overview ## Chain of trust [#chain-of-trust] When holder applications request [wallet attestation](/docs/holding/credential-claiming-guides/wallet-attestation) tokens from their tethered MATTR VII tenant, issuers need a way to confirm the wallet's identity and decide whether to trust these tokens. This is accomplished using a [chain of trust](/docs/concepts/chain-of-trust), a hierarchy of certificates that proves the wallet's authenticity. The following diagram depicts how MATTR implements the chain of trust model when signing wallet attestation tokens: Chain of trust 1. **Holder root CA certificate**: The root of the trust chain. A self-signed X.509 certificate that identifies the holder application operator. It is used to sign wallet attestation signer certificates. 2. **Wallet Attestation Signer Certificate (WASC)**: An end-entity X.509 certificate signed by the Holder root CA. It is used to sign wallet attestation JWTs that are presented to credential issuers as [Wallet Attestation](/docs/holding/credential-claiming-guides/wallet-attestation) proofs during credential claiming flows. 3. **Wallet attestation JWT**: The end-entity of the chain. A signed JWT presented to the credential issuer that attests the holder application's identity and authenticity. ## Managed vs. unmanaged certificates [#managed-vs-unmanaged-certificates] MATTR VII supports both managed and unmanaged (external) Holder root CA certificates, giving you flexibility in how you manage your certificate infrastructure. ### Managed certificates [#managed-certificates] With managed certificates, MATTR VII generates and manages the Holder root CA certificate and its private key on your behalf. Wallet attestation signers are automatically provisioned on demand when the first wallet attestation request is made, and you do not need to create them explicitly. This is the simplest approach and is recommended for most use cases. ### Unmanaged (external) certificates [#unmanaged-external-certificates] With unmanaged certificates, you supply your own externally-managed Holder root CA in PEM format. You are then responsible for: * Generating and protecting the Holder root CA certificate and its private key. * Creating wallet attestation signers and using the returned Certificate Signing Request (CSR) to obtain signed certificates from your root CA. * Uploading the signed certificates to MATTR VII. * Managing certificate rotation and lifecycle. This approach gives you full control over the certificate chain but requires more operational overhead. See the [external certificates](/docs/concepts/chain-of-trust#external-certificates) documentation for more details on the unmanaged certificate model. ## Certificate limits [#certificate-limits] * A maximum of **three** Holder root CA certificates can be created per tenant. * Only **one** Holder root CA can be active at a time. Activating a new root automatically deactivates the previously active one. * A maximum of **five** wallet attestation signers can be created per Holder root CA certificate. ## Certificate requirements [#certificate-requirements] The following lists depict the requirements for certificates used in the holder certificate chain. Some requirements are common across all certificates, while others are specific to the type of certificate (Holder root CA, wallet attestation signer certificate). * When using **managed holder certificates**, MATTR VII **automatically** ensures that all certificates meet these requirements. * When using **unmanaged holder certificates**, it is the responsibility of the **holder application operator** to ensure compliance. ### Common certificate requirements [#common-certificate-requirements] * Certificate format & basic attributes: * PEM format must contain a valid X.509 certificate. * Version must be v3. * `Issuer` field must be present and valid. * Issuer Alternative Name must be present and contain a valid email address or URI. * Serial Number: * Must be present. * Must contain 1-20 digits (**Best practice**: Use a positive, non-sequential value). * Subject attributes: * Subject field must be present: * Country (C): must be present and be a valid [ISO 3166-1 alpha-2 code](https://www.iso.org/glossary-for-iso-3166.html). * Common Name (CN): must be present and non-empty. * Public Key requirements: * Subject Public Key must be present. * Extensions: * Certificate must include extensions. * Duplicate extensions are not allowed (No more than one extension with the same `extnID`). * Mandatory extensions: * Subject Key Identifier: Must be present and non-empty. * Key Usage: * Must be present. * Must match the intended use of the certificate (e.g. Holder root CA, wallet attestation signer). * Validity period: * `NotAfter` must be after `NotBefore`. * `NotAfter` cannot be in the past (expired certificates are invalid). * Future dated certificates are valid (e.g. `notBefore` can be in the future). * Certificate Revocation List (CRL): * If a CRL is provided, it must be valid and signed by the Holder root CA. * The CRL must be accessible via a valid URI. ### Holder root CA specific requirements [#holder-root-ca-specific-requirements] * Must include the `keyCertSign` and `cRLSign` key usages. * Basic constraints must be present and `CA` must be set to `TRUE`. * Issuer Alternative Name must be present and contain a valid email address or URI. * Signature must be self-signed and verifiable. * Public key must use one of the supported public key algorithms and curves: * ECDSA curves: `P-256`, `P-384`, `P-521`, `brainpoolP256r1`, `brainpoolP320r1`, `brainpoolP384r1`, `brainpoolP512r1` * EdDSA key types: `Ed25519`, `Ed448` ### Wallet attestation signer certificate specific requirements [#wallet-attestation-signer-certificate-specific-requirements] * Must be signed by a valid Holder root CA. * Common name must differ from parent/root Holder root CA. * `Issuer` field must be present and must match the exact binary value of the Holder root CA certificate subject. * Must include the `digitalSignature` key usage exclusively. * Signature must be verifiable against the Holder root CA. * Authority Key Identifier must be present and match the Holder root CA's Subject Key Identifier. * Must not exceed parent Holder root CA's validity period (i.e. `notBefore` and `notAfter` must be within the Holder root CA's validity period). * Public key must match the CSR provided during wallet attestation signer creation. # How to configure authentication requirements for credentials using Device Key Authentication URL: /docs/holding/credential-claiming-guides/device-key-authentication-guide ## Overview [#overview] This guide demonstrates how to use **Device Key Authentication** to control what user authentication methods (such as face authentication, fingerprint authentication, or the device passcode) are required when claiming and presenting credentials in your wallet applications. When the SDK is initialized, the `UserAuthConfiguration` structure defines the default authentication behavior for the wallet application, including settings like when authentication is required and the timeout duration. Device Key Authentication gives you fine-grained control over the security posture of specific credentials. By specifying a per-credential authentication policy, you can require stronger methods for sensitive credentials while allowing more convenient access for less sensitive ones. When you specify a `DeviceKeyAuthenticationPolicy` during credential claiming or presentation, it overrides the general `UserAuthConfiguration` settings for that specific operation. This means that the authentication requirements defined in the `DeviceKeyAuthenticationPolicy` take precedence over the broader wallet-level settings. Conversely, if no `DeviceKeyAuthenticationPolicy` is specified, the SDK falls back to the `UserAuthConfiguration` settings for authentication behavior. Each policy leverages whatever user authentication modalities are currently configured at the operating system level (e.g., enrolled face authentication / fingerprints and an active device passcode). The wallet does not manage biometric enrollment. It relies on the device’s Secure Enclave / Keystore settings. By choosing the appropriate policy, you align credential protection directly with the user’s device-level security configuration. ## Prerequisites [#prerequisites] This guide builds on the [Credential Claiming tutorial](/docs/holding/credential-claiming-tutorial). It is recommended to complete that tutorial first, then return here to learn how to configure device key authentication policies. ## Understanding Device Key Authentication [#understanding-device-key-authentication] ### What is Device Key Authentication? [#what-is-device-key-authentication] Device Key Authentication is a feature that allows you to specify which type of user authentication is required to access the cryptographic keys protecting each credential. These keys are generated on the user's device and are used for: 1. **Secure credential claiming**: Authenticate the user when a new credential is being claimed and stored in the wallet. 2. **Secure credential usage**: Authenticate the user when an existing credential is accessed, either to view it in the wallet or to present it to a verifier (in-person or remotely). By configuring a device key authentication policy, you can define the level of security required for each credential based on its sensitivity and use case. Device Key Authentication enables you to: * **Match security to sensitivity**: Require stronger authentication (like biometrics) for high-value credentials such as government IDs, while allowing passcode-only access for lower-risk credentials like membership cards. * **Improve user experience**: Balance security with convenience by allowing users to choose appropriate authentication methods for different credential types. * **Maintain consistency**: Ensure the same authentication method used during credential claiming is required during presentation, providing a predictable and secure user experience. * **Meet compliance requirements**: Satisfy regulatory or organizational requirements that mandate specific authentication methods for certain credential types. ### How it works [#how-it-works] When you configure a device key authentication policy for a credential: 1. **During claiming**: The user must authenticate using the specified method before the credential can be stored in their wallet. 2. **During access**: The same authentication method is required before the credential can be accessed, either to view it in the wallet or to present it to a verifier (in-person or remotely). The authentication policy is bound to the device key that protects the credential, ensuring consistent security throughout the credential's lifecycle. ## Available authentication options [#available-authentication-options] The Holder SDKs provide several authentication policy options: The iOS Holder SDK authentication policy options map directly to Apple's [SecAccessControlCreateFlags](https://developer.apple.com/documentation/security/secaccesscontrolcreateflags): | UserAuthenticationType | Biometry | Credential valid after biometry changed | Lockscreen | Security level | | ---------------------- | :------: | :-------------------------------------: | :--------: | :------------: | | `biometryCurrentSet` | ✅ | ❌ | ❌ | Highest | | `biometryAny` | ✅ | ✅ | ❌ | High | | `deviceCredential` | ❌ | ✅ | ✅ | Medium | | `userPresence` | ✅ | ✅ | ✅ | Lowest | * `biometryAny`: Adding or removing biometric data does not invalidate keys (e.g., user adds a new TouchID enrollment or deletes an existing FaceID enrollment after claiming the credential. Existing device keys remain usable and no re-provisioning is required). Maps to Apple's `SecAccessControlCreateFlags.biometryAny`. * `biometryCurrentSet`: Adding or removing biometric data invalidates the key (e.g., user adds a new TouchID enrollment or deletes an existing FaceID enrollment after claiming the credential. The underlying device key becomes unusable and the credential is invalid. The user must re-claim the credential from the issuer). Maps to Apple's `SecAccessControlCreateFlags.biometryCurrentSet`. * `deviceCredential`: Key remains valid if passcode changes but becomes invalid if passcode is removed. Maps to Apple's `SecAccessControlCreateFlags.devicePasscode`. * `userPresence`: Requires user presence (biometry or passcode) each access. Adding/removing biometry does not invalidate keys. Maps to Apple's `SecAccessControlCreateFlags.userPresence`. If a credential’s authentication policy differs from the policy set at SDK initialization, the user may see two authentication prompts. One prompt enforces the SDK-level policy; the other enforces the credential’s policy (each tied to a different key). This can occur when: * The user attempts to retrieve or access a credential shortly after initialization. * At least five minutes have passed since initialization and the user then attempts to retrieve or access a credential. | UserAuthenticationType | Biometry | Credential valid after biometry changed | Lockscreen | Security level | | ---------------------- | :------: | :-------------------------------------: | :--------: | :------------: | | `BiometryCurrentSet` | ✅ | ❌ | ❌ | Highest | | `BiometryAny` | ✅ | ✅ | ❌ | High | | `DeviceCredential` | ❌ | ✅ | ✅ | Medium | | `UserPresence` | ✅ | ✅ | ✅ | Lowest | * `BiometryAny`: Adding or removing biometric data does not invalidate keys (e.g., user adds a new fingerprint or deletes an existing face authentication enrollment after claiming the credential. Existing device keys remain usable and no re-provisioning is required). * `BiometryCurrentSet`: Adding or removing biometric data invalidates the key (e.g., user adds a new fingerprint or deletes an existing face authentication enrollment after claiming the credential. The underlying device key becomes unusable and the credential is invalid. The user must re-claim the credential from the issuer). * `DeviceCredential`: Key remains valid if passcode changes but becomes invalid if passcode is removed. * `UserPresence`: Requires user presence (biometry or passcode) each access. Adding/removing biometry does not invalidate keys. The React Native Holder SDK uses the `DeviceKeyAuthenticationType` enum for authentication policy options: | DeviceKeyAuthenticationType | Biometry | Credential valid after biometry changed | Lockscreen | Security level | | --------------------------- | :------: | :-------------------------------------: | :--------: | :------------: | | `BiometryCurrentSet` | ✅ | ❌ | ❌ | Highest | | `BiometryAny` | ✅ | ✅ | ❌ | High | | `DeviceCredential` | ❌ | ✅ | ✅ | Medium | | `UserPresence` | ✅ | ✅ | ✅ | Lowest | * `BiometryAny`: Adding or removing biometric data does not invalidate keys (e.g., user adds a new fingerprint or deletes an existing face authentication enrollment after claiming the credential. Existing device keys remain usable and no re-provisioning is required). * `BiometryCurrentSet`: Adding or removing biometric data invalidates the key (e.g., user adds a new fingerprint or deletes an existing face authentication enrollment after claiming the credential. The underlying device key becomes unusable and the credential is invalid. The user must re-claim the credential from the issuer). * `DeviceCredential`: As long as the user has unlocked their phone, they have access to the key. Any authentication method is allowed. * `UserPresence`: Requires user presence (biometry or passcode) on each access to the key. Any authentication method is allowed. Adding/removing biometry does not invalidate keys. On iOS devices, if a credential’s authentication policy differs from the policy set at SDK initialization, the user may see two authentication prompts. One prompt enforces the SDK-level policy; the other enforces the credential’s policy (each tied to a different key). This can occur when: * The user attempts to retrieve or access a credential shortly after initialization. * At least five minutes have passed since initialization and the user then attempts to retrieve or access a credential. ## Configuring Device Key Authentication [#configuring-device-key-authentication] To configure device key authentication, you need to specify a `DeviceKeyAuthenticationPolicy` when calling the `retrieveCredentials` method during the credential claiming flow. ### Step 1: Create a DeviceKeyAuthenticationPolicy [#step-1-create-a-devicekeyauthenticationpolicy] First, create a `DeviceKeyAuthenticationPolicy` object that defines the authentication requirements: ```swift title="ContentView" let deviceKeyAuthPolicy = DeviceKeyAuthenticationPolicy( type: .biometryAny ) ``` The `DeviceKeyAuthenticationPolicy` takes a single parameter: * `type`: The type of user authentication required. Choose from one of the options detailed [above](#available-authentication-options). First, create a `DeviceKeyAuthenticationPolicy` object that defines the authentication requirements: ```kotlin title="MainActivity.kt" val deviceKeyAuthPolicy = DeviceKeyAuthenticationPolicy( type = UserAuthenticationType.BiometryAny ) ``` The `DeviceKeyAuthenticationPolicy` takes a single parameter: * `userAuthenticationType`: The type of user authentication required. Choose from one of the options detailed [above](#available-authentication-options). First, import the required types and create a `DeviceKeyAuthenticationPolicy` object that defines the authentication requirements: ```tsx title="App.tsx" import { DeviceKeyAuthenticationType, } from "@mattrglobal/mobile-credential-holder-react-native"; const authenticationPolicy = { type: DeviceKeyAuthenticationType.BiometryAny, }; ``` The `DeviceKeyAuthenticationPolicy` takes a single parameter: * `type`: The type of user authentication required. Choose from one of the `DeviceKeyAuthenticationType` options detailed [above](#available-authentication-options). ### Step 2: Pass the policy to retrieveCredentials [#step-2-pass-the-policy-to-retrievecredentials] Update your `retrieveCredential` function from the [Credential Claiming tutorial](/docs/holding/credential-claiming-tutorial#step-4-retrieve-the-credential) to include the `deviceKeyAuthenticationPolicy` parameter: ```swift title="ContentView" func retrieveCredential(transactionCode: String?) { Task { do { // Create the device key authentication policy let deviceKeyAuthPolicy = DeviceKeyAuthenticationPolicy( type: .biometryAny ) let retrievedCredentialResults = try await mobileCredentialHolder.retrieveCredentials( credentialOffer: discoveredCredentialOfferURL, clientId: Constants.clientId, transactionCode: transactionCode, deviceKeyAuthenticationPolicy: deviceKeyAuthPolicy // [!code focus] ) Task { var credentials: [MobileCredential] = [] for result in retrievedCredentialResults { switch result { case .success(_, let credentialId): if let credential = try? await mobileCredentialHolder.getCredential(credentialId: credentialId) { credentials.append(credential) } case .failure(let docType, let error): print("Failed to retrieve \(docType): \(error)") } } self.retrievedCredentials = credentials navigationPath = NavigationPath() navigationPath.append(NavigationState.retrievedCredentials) } } catch { print(error.localizedDescription) } } } ``` Update your `onRetrieveCredentials` function from the [Credential Claiming tutorial](/docs/holding/credential-claiming-tutorial#step-4-retrieve-the-credential) to include the `DeviceKeyAuthenticationPolicy` parameter: ```kotlin title="MainActivity.kt" private fun onRetrieveCredentials( coroutineScope: CoroutineScope, activity: Activity, navController: NavController, transactionCode: String ) { coroutineScope.launch { try { val deviceKeyAuthPolicy = DeviceKeyPolicy( DeviceKeyAuthenticationPolicy(type = UserAuthenticationType.BiometryAny) ) val mdocHolder = MobileCredentialHolder.getInstance() val retrieveCredentialResults = mdocHolder.retrieveCredentials( activity, SharedData.scannedOffer!!, clientId = "android-mobile-credential-tutorial-holder-app", transactionCode = transactionCode, deviceKeyAuthPolicy ) SharedData.retrievedCredentials = retrieveCredentialResults.mapNotNull { result -> when (result) { is RetrieveCredentialResult.Success -> try { mdocHolder.getCredential(result.credentialId, fetchUpdatedStatusList = false) } catch (e: Exception) { val msg = "Failed to get credential from storage" Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show() null } is RetrieveCredentialResult.Failure -> { val msg = "Failed to retrieve ${result.docType}: ${result.error}" Toast.makeText(activity, msg, Toast.LENGTH_SHORT).show() null } } } navController.navigate("retrievedCredential") SharedData.discoveredCredentialOffer = null } catch (e: Exception) { Toast.makeText(activity, "Failed to retrieve credentials", Toast.LENGTH_SHORT).show() } } } ``` Update your `retrieveCredentials` call from the [Credential Claiming tutorial](/docs/holding/credential-claiming-tutorial#step-4-retrieve-the-credential) to include the `authenticationPolicy` parameter: ```tsx title="App.tsx" import { retrieveCredentials, getCredential, DeviceKeyAuthenticationType, } from "@mattrglobal/mobile-credential-holder-react-native"; const onRetrieveCredentials = async ( credentialOfferUrl: string, transactionCode?: string, ) => { // Create the device key authentication policy const authenticationPolicy = { type: DeviceKeyAuthenticationType.BiometryAny, }; const retrieveResult = await retrieveCredentials({ credentialOffer: credentialOfferUrl, clientId: "react-native-mobile-credential-tutorial-holder-app", transactionCode, authenticationPolicy, // [!code focus] }); if (retrieveResult.isErr()) { // Handle error return; } const results = retrieveResult.value; for (const result of results) { if (result.isSuccess) { const credentialResult = await getCredential({ credentialId: result.credentialId, fetchUpdatedStatusList: false, }); // Process credential } else { // Handle result.error for result.docType } } }; ``` ## Understanding the complete flow [#understanding-the-complete-flow] When you call `retrieveCredentials` with a `deviceKeyAuthenticationPolicy`: 1. The SDK generates a device key with the specified authentication requirements. 2. The user is prompted to authenticate using the specified method (e.g., Face ID or fingerprint). 3. The authentication requirement is bound to the access control of the generated device key, and that key is cryptographically bound to the credential. 4. Once authenticated, the credential is claimed and securely stored. 5. Future access to this credential (during presentation workflows) will require the same authentication method. ## Verifying the authentication policy [#verifying-the-authentication-policy] You can inspect the authentication policy associated with a credential by accessing its metadata: ```swift title="Inspecting authentication policy" if let credential = try? await mobileCredentialHolder.getCredential(credentialId: credentialId) { if let authInfo = credential.metadata.deviceKeyAuthenticationInfo { print("User authentication type: \(authInfo.type?.rawValue ?? "not set")") } } ``` The `deviceKeyAuthenticationInfo` property returns a `DeviceKeyAuthenticationInfo` object containing: * `type`: The authentication type configured for this credential's device key. ```kotlin title="Inspecting authentication policy" val credentialMetadata = mobileCredentialHolder.getCredentials().first { it.id == credentialId } credentialMetadata.deviceKeyPolicy?.authentication?.type?.let { println("User authentication type: $it") } ``` The `deviceKeyPolicy` property returns a `DeviceKeyPolicyInfo` object with `authentication` property, containing: * `type`: The authentication type configured for this credential's device key. * `timeoutSeconds`: Timeout for user authentication. ```tsx title="Inspecting authentication policy" import { getCredential, } from "@mattrglobal/mobile-credential-holder-react-native"; const credentialResult = await getCredential({ credentialId, fetchUpdatedStatusList: false, }); if (credentialResult.isOk()) { const credential = credentialResult.value; const authInfo = credential.metadata.deviceKeyAuthenticationInfo; if (authInfo) { console.log(`User authentication type: ${authInfo.type}`); } } ``` The `deviceKeyAuthenticationInfo` property returns a `DeviceKeyAuthenticationInfo` object containing: * `type`: The authentication type configured for this credential's device key. ## Testing Device Key Authentication [#testing-device-key-authentication] To test the device key authentication feature: 1. **Complete the credential claiming flow** with a configured `deviceKeyAuthenticationPolicy` as described above. 2. **Observe the authentication prompt** during claiming (e.g., Face ID, Touch ID, or passcode prompt). 3. **Attempt to present the credential** using either: * [Proximity presentation](/docs/holding/proximity-presentation-tutorial) * [Remote presentation](/docs/holding/remote-presentation-tutorial) 4. **Verify the same authentication method** is required during presentation. Testing with different authentication policies requires re-claiming credentials, as the policy is bound to the device key generated during the claiming process. # URI scheme handling in your holder application URL: /docs/holding/credential-claiming-guides/handling-uri-schemes Description: Learn how to configure your holder application to handle different types of credential offer URI schemes from issuers ## Overview [#overview] When issuers create credential offers, they can use different URI schemes to control which wallet applications can claim those offers and how the user experience flows. Your holder application needs to be properly configured to handle the URI schemes that issuers in your ecosystem are using. This guide explains the different URI scheme types and shows you how to configure your holder application to handle each type. ## Understanding URI schemes in credential claiming [#understanding-uri-schemes-in-credential-claiming] A URI scheme is the protocol part at the beginning of a URI (such as `https://`, `mailto://`, or custom schemes like `openid-credential-offer://`). When an issuer generates a credential offer, they choose which URI scheme to use based on their requirements for security, user experience, and control over which apps can handle the offer. The three main URI scheme types used for credential offers are: 1. **Standard OID4VCI custom scheme** (`openid-credential-offer://`) - The baseline scheme defined by the OID4VCI specification. Any app registered to handle this scheme can respond to the offer. 2. **Private-use URI scheme** (`com.example.wallet://`) - A unique custom scheme using reverse-domain notation. This makes it less likely (but not impossible) for other apps to handle the offer. 3. **Claimed HTTPS scheme** (`https://example.com/wallet/...`) - Uses domain-verified App Links (Android) or Universal Links (iOS) to ensure only your specific app can handle offers from your domain. For a detailed explanation of how these schemes work, their trade-offs, and security considerations, see the [Credential offer documentation](/docs/issuance/credential-offer/overview#controlling-how-credentials-are-claimed). ## Prerequisites [#prerequisites] This guide assumes you have: * Completed the [Credential Claiming Tutorial](/docs/holding/credential-claiming-tutorial) and have a working holder application. All examples in this guide build on top of the tutorial application. * Understanding of how [credential offers](/docs/issuance/credential-offer/overview) are created and structured. * Access to modify your application configuration and code. For HTTPS schemes (App Links/Universal Links), you will also need: * A domain you control. * Ability to host verification files on your web server. ## Configuring URI scheme handling [#configuring-uri-scheme-handling] The configuration required depends on which URI scheme types you want to support and which platform you're developing for. ### Standard OID4VCI custom scheme [#standard-oid4vci-custom-scheme] The standard `openid-credential-offer://` scheme is already configured in the tutorial application. No additional setup is required unless you removed it during development. **Verify scheme configuration** 1. Open your project in Xcode. 2. Select your app target and navigate to the **Info** tab. 3. Expand **URL Types** and verify an entry exists with: * **Identifier**: `openid-credential-offer` * **URL Schemes**: `openid-credential-offer` **Verify intent filter configuration** 1. Open your `AndroidManifest.xml` file and verify the following intent filter exists within your main activity: ```xml title="AndroidManifest.xml" ``` **Verify scheme configuration** 1. Open your `app.config.ts` file and verify the scheme is properly configured: ```ts title="app.config.ts" export default ({ config }: ConfigContext): ExpoConfig => ({ // ... other config scheme: "openid-credential-offer", // ... other config }); ``` For Expo projects, this configuration automatically sets up the scheme for both iOS and Android. ### Private-use URI scheme [#private-use-uri-scheme] To handle offers using a custom scheme like `com.yourcompany.wallet://`, you need to register that unique scheme with the operating system. **Step 1: Register your custom scheme** 1. Open your project in Xcode. 2. Select your app target and navigate to the **Info** tab. 3. Expand **URL Types** and select the **+** button. 4. Enter: * **Identifier**: `com.yourcompany.wallet` * **URL Schemes**: `com.yourcompany.wallet` **Step 2: Handle incoming URLs** In your `ContentView.swift` file, add a handler for the custom scheme URLs. Add the following extension to your ViewModel: ```swift title="ContentView.swift" // MARK: Handle URL extension ViewModel { func handleIncomingURL(_ url: URL) { guard url.scheme == "com.yourcompany.wallet" else { return } // Extract the credential offer from the URL // The offer is typically base64-encoded in the path or as a query parameter if let offerString = extractCredentialOffer(from: url) { discoverCredentialOffer(offerString) navigationPath.append(NavigationState.credentialOffer) } } func extractCredentialOffer(from url: URL) -> String? { // Example: com.yourcompany.wallet://accept/BASE64_ENCODED_OFFER if url.host == "accept", let encodedOffer = url.pathComponents.last, let data = Data(base64URLEncoded: encodedOffer), let decodedOffer = String(data: data, encoding: .utf8) { return decodedOffer } // Alternative: com.yourcompany.wallet://accept?offer=BASE64_ENCODED_OFFER if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let offerParam = components.queryItems?.first(where: { $0.name == "offer" })?.value, let data = Data(base64URLEncoded: offerParam), let decodedOffer = String(data: data, encoding: .utf8) { return decodedOffer } return nil } } // Helper extension for base64 URL decoding extension Data { init?(base64URLEncoded string: String) { var base64 = string .replacingOccurrences(of: "-", with: "+") .replacingOccurrences(of: "_", with: "/") let paddingLength = (4 - base64.count % 4) % 4 base64.append(String(repeating: "=", count: paddingLength)) self.init(base64Encoded: base64) } } ``` Then, add an `onOpenURL` modifier in `ContentView` to handle incoming custom scheme URLs: ```swift title="ContentView.swift" .onOpenURL { url in viewModel.handleIncomingURL(url) } ``` If your app already handles other types of links, you'll need to update the `handleIncomingURL` method to support multiple link types. **Step 1: Add intent filter for custom scheme** Open your `AndroidManifest.xml` file and add a new intent filter for your custom scheme: ```xml title="AndroidManifest.xml" ``` **Step 2: Handle incoming intents** In your `MainActivity.kt` file, add handling for the custom scheme URLs: ```kotlin title="MainActivity.kt" class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Handle intent when activity is created handleIntent(intent) setContent { // ... existing UI code } } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) // Handle intent when activity is already running handleIntent(intent) } private fun handleIntent(intent: Intent?) { val data = intent?.data ?: return when (data.scheme) { "openid-credential-offer" -> { // Standard scheme handling (already implemented in tutorial) val credentialOffer = data.toString() navigateToOfferScreen(credentialOffer) } "com.yourcompany.wallet" -> { // Custom scheme handling val credentialOffer = extractCredentialOffer(data) if (credentialOffer != null) { navigateToOfferScreen(credentialOffer) } } } } private fun extractCredentialOffer(uri: Uri): String? { // Example: com.yourcompany.wallet://accept/BASE64_ENCODED_OFFER val encodedOffer = uri.lastPathSegment ?: uri.getQueryParameter("offer") ?: return null return try { val decodedBytes = android.util.Base64.decode( encodedOffer.replace('-', '+').replace('_', '/'), android.util.Base64.URL_SAFE or android.util.Base64.NO_PADDING ) String(decodedBytes, Charsets.UTF_8) } catch (e: IllegalArgumentException) { null } } private fun navigateToOfferScreen(credentialOffer: String) { // Navigate to your offer screen with the credential offer // Implementation depends on your navigation setup } } ``` **Step 1: Update scheme configuration** Open your `app.config.ts` file and update the scheme to your custom scheme: ```ts title="app.config.ts" export default ({ config }: ConfigContext): ExpoConfig => ({ // ... other config scheme: "com.yourcompany.wallet", // ... other config }); ``` **Step 2: Handle incoming URLs** In your `/app/_layout.tsx` file, add URL handling using Expo's Linking API: ```tsx title="/app/_layout.tsx" import { useEffect } from "react"; import * as Linking from "expo-linking"; import { useRouter } from "expo-router"; export default function RootLayout() { const router = useRouter(); useEffect(() => { // Handle URL when app is already open const subscription = Linking.addEventListener("url", ({ url }) => { handleIncomingURL(url); }); // Handle URL when app is opened from a closed state Linking.getInitialURL().then((url) => { if (url) { handleIncomingURL(url); } }); return () => { subscription.remove(); }; }, []); const handleIncomingURL = (url: string) => { const parsed = Linking.parse(url); // Handle custom scheme URLs if (parsed.scheme === "com.yourcompany.wallet" && parsed.hostname === "accept") { const credentialOffer = extractCredentialOffer(url); if (credentialOffer) { router.push({ pathname: "/claim-credential", params: { scannedValue: credentialOffer }, }); } } }; const extractCredentialOffer = (url: string): string | null => { try { const parsed = Linking.parse(url); // Extract from path: com.yourcompany.wallet://accept/BASE64_ENCODED_OFFER const encodedOffer = (parsed.path ? parsed.path.split("/").filter(Boolean).pop() : undefined) ?? parsed.queryParams?.offer; if (!encodedOffer) return null; // Decode base64 URL-encoded offer const base64 = encodedOffer .replace(/-/g, "+") .replace(/_/g, "/") .padEnd(encodedOffer.length + ((4 - (encodedOffer.length % 4)) % 4), "="); return atob(base64); } catch (error) { console.error("Failed to extract credential offer:", error); return null; } }; return ( ); } ``` **Step 3: Rebuild native projects** After changing the scheme configuration, regenerate the native projects: ```bash title="Regenerate native projects" yarn expo prebuild --clean ``` ### Claimed HTTPS scheme (App Links / Universal Links) [#claimed-https-scheme-app-links--universal-links] HTTPS schemes provide the most secure and reliable way to ensure only your specific app handles credential offers from your domain. This requires domain ownership and proper configuration. **Step 1: Create the Apple App Site Association file** This step must be performed by the Credential Issuer or the party controlling the domain used in the HTTPS scheme, as it requires hosting a specific file on the web server. If you are both the issuer and holder, you can complete this step yourself. Otherwise, you will need to coordinate with the issuer to ensure this file is created and hosted correctly. Create a file named `apple-app-site-association` (no file extension) with the following content: ```json title="apple-app-site-association" { "applinks": { "apps": [], "details": [ { "appID": "TEAM_ID.com.yourcompany.wallet", "paths": ["/wallet/*"] } ] } } ``` Replace: * `TEAM_ID` with your Apple Developer Team ID (found in your Apple Developer account). * `com.yourcompany.wallet` with your app's bundle identifier. * `/wallet/*` with the path pattern you want to handle (you can use multiple paths). **Step 2: Host the association file** Upload the file to your web server at: ``` https://yourdomain.com/.well-known/apple-app-site-association ``` Or directly at the root: ``` https://yourdomain.com/apple-app-site-association ``` Ensure: * The file is served with `Content-Type: application/json`. * The file is accessible via HTTPS without redirects. * No `.json` extension is added to the filename. **Step 3: Enable Associated Domains capability** 1. Open your project in Xcode. 2. Select your app target and navigate to **Signing & Capabilities**. 3. Select **+ Capability** and add **Associated Domains**. 4. Add your domain in the format: `applinks:yourdomain.com` **Step 4: Handle Universal Links** In your `ContentView.swift` file, add a handler for Universal Links. Add the following extension to your ViewModel: ```swift title="ContentView.swift" // MARK: Handle Universal Links extension ViewModel { func handleIncomingURL(_ url: URL) { guard url.scheme == "https" && url.host == "yourdomain.com" else { return } // Extract the credential offer from the URL // Example: https://yourdomain.com/wallet/accept?offer=BASE64_ENCODED_OFFER if let offerString = extractCredentialOffer(from: url) { discoverCredentialOffer(offerString) navigationPath.append(NavigationState.credentialOffer) } } func extractCredentialOffer(from url: URL) -> String? { // Verify path contains required components guard url.pathComponents.contains("wallet"), url.pathComponents.contains("accept") else { return nil } // Extract offer from query parameter if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let offerParam = components.queryItems?.first(where: { $0.name == "offer" })?.value, let data = Data(base64URLEncoded: offerParam), let decodedOffer = String(data: data, encoding: .utf8) { return decodedOffer } return nil } } // Helper extension for base64 URL decoding extension Data { init?(base64URLEncoded string: String) { var base64 = string .replacingOccurrences(of: "-", with: "+") .replacingOccurrences(of: "_", with: "/") let paddingLength = (4 - base64.count % 4) % 4 base64.append(String(repeating: "=", count: paddingLength)) self.init(base64Encoded: base64) } } ``` Then, add an `onOpenURL` modifier in `ContentView` to handle incoming universal links: ```swift title="ContentView.swift" .onOpenURL { url in viewModel.handleIncomingURL(url) } ``` If your app already handles other types of links, you'll need to update the `handleIncomingURL` method to support multiple link types. **Step 1: Create the Digital Asset Links file** This step must be performed by the Credential Issuer or the party controlling the domain used in the HTTPS scheme, as it requires hosting a specific file on the web server. If you are both the issuer and holder, you can complete this step yourself. Otherwise, you will need to coordinate with the issuer to ensure this file is created and hosted correctly. Create a file named `assetlinks.json` with the following content: ```json title="assetlinks.json" [ { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.yourcompany.wallet", "sha256_cert_fingerprints": [ "YOUR_APP_SHA256_FINGERPRINT" ] } } ] ``` Replace: * `com.yourcompany.wallet` with your app's package name. * `YOUR_APP_SHA256_FINGERPRINT` with your app's SHA-256 certificate fingerprint. To get your SHA-256 fingerprint, run: ```bash title="Get SHA-256 fingerprint" keytool -list -v -keystore your-release-key.keystore ``` Or for debug builds: ```bash title="Get debug SHA-256 fingerprint" keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android ``` **Step 2: Host the association file** Upload the file to your web server at: ``` https://yourdomain.com/.well-known/assetlinks.json ``` Ensure: * The file is served with `Content-Type: application/json`. * The file is accessible via HTTPS without redirects. **Step 3: Add App Links intent filter** Open your `AndroidManifest.xml` and add an intent filter with `android:autoVerify="true"`: ```xml title="AndroidManifest.xml" ``` **Step 4: Handle App Links** Update your `MainActivity.kt` to handle App Links: ```kotlin title="MainActivity.kt" class MainActivity : ComponentActivity() { private fun handleIntent(intent: Intent?) { val data = intent?.data ?: return when { // Handle App Links (HTTPS URLs) data.scheme == "https" && data.host == "yourdomain.com" -> { handleAppLink(data) } // Handle custom schemes data.scheme == "openid-credential-offer" || data.scheme == "com.yourcompany.wallet" -> { handleCustomScheme(data) } } } private fun handleAppLink(uri: Uri) { // Example: https://yourdomain.com/wallet/accept?offer=BASE64_ENCODED_OFFER if (uri.pathSegments.contains("wallet") && uri.pathSegments.contains("accept")) { val encodedOffer = uri.getQueryParameter("offer") ?: return val credentialOffer = extractCredentialOffer(encodedOffer) if (credentialOffer != null) { navigateToOfferScreen(credentialOffer) } } } private fun handleCustomScheme(uri: Uri) { // ... existing custom scheme handling } private fun extractCredentialOffer(encodedOffer: String): String? { return try { val decodedBytes = android.util.Base64.decode( encodedOffer.replace('-', '+').replace('_', '/'), android.util.Base64.URL_SAFE or android.util.Base64.NO_PADDING ) String(decodedBytes, Charsets.UTF_8) } catch (e: IllegalArgumentException) { null } } } ``` **Step 5: Verify App Links** After installing your app, verify that App Links are working: ```bash title="Verify App Links" adb shell am start -a android.intent.action.VIEW -d "https://yourdomain.com/wallet/accept?offer=test" ``` You can also check the verification status: ```bash title="Check verification status" adb shell pm get-app-links com.yourcompany.wallet ``` For React Native with Expo, configuring Universal Links (iOS) and App Links (Android) requires additional setup in the app configuration. **Step 1: Create association files** This step must be performed by the Credential Issuer or the party controlling the domain used in the HTTPS scheme, as it requires hosting a specific file on the web server. If you are both the issuer and holder, you can complete this step yourself. Otherwise, you will need to coordinate with the issuer to ensure this file is created and hosted correctly. Follow the instructions in the iOS and Android tabs to create and host: * `apple-app-site-association` file at `https://yourdomain.com/.well-known/apple-app-site-association` * `assetlinks.json` file at `https://yourdomain.com/.well-known/assetlinks.json` **Step 2: Configure app for Universal Links / App Links** Update your `app.config.ts`: ```ts title="app.config.ts" export default ({ config }: ConfigContext): ExpoConfig => ({ // ... other config ios: { bundleIdentifier: "com.yourcompany.wallet", associatedDomains: ["applinks:yourdomain.com"], // ... other iOS config }, android: { package: "com.yourcompany.wallet", intentFilters: [ { action: "VIEW", autoVerify: true, data: [ { scheme: "https", host: "yourdomain.com", pathPrefix: "/wallet", }, ], category: ["BROWSABLE", "DEFAULT"], }, ], // ... other Android config }, // ... other config }); ``` **Step 3: Handle Universal Links / App Links** Update your `/app/_layout.tsx` to handle HTTPS URLs: ```tsx title="/app/_layout.tsx" import { useEffect } from "react"; import * as Linking from "expo-linking"; import { useRouter } from "expo-router"; export default function RootLayout() { const router = useRouter(); useEffect(() => { const subscription = Linking.addEventListener("url", ({ url }) => { handleIncomingURL(url); }); Linking.getInitialURL().then((url) => { if (url) { handleIncomingURL(url); } }); return () => { subscription.remove(); }; }, []); const handleIncomingURL = (url: string) => { const parsed = Linking.parse(url); // Handle Universal Links / App Links (HTTPS) if (parsed.scheme === "https" && parsed.hostname === "yourdomain.com") { handleAppLink(url); return; } // Handle custom schemes if ( parsed.scheme === "openid-credential-offer" || parsed.scheme === "com.yourcompany.wallet" ) { handleCustomScheme(url); return; } }; const handleAppLink = (url: string) => { const parsed = Linking.parse(url); // Example: https://yourdomain.com/wallet/accept?offer=BASE64_ENCODED_OFFER if (parsed.path?.includes("/wallet/accept")) { const encodedOffer = parsed.queryParams?.offer as string; if (encodedOffer) { const credentialOffer = extractCredentialOffer(encodedOffer); if (credentialOffer) { router.push({ pathname: "/claim-credential", params: { scannedValue: credentialOffer }, }); } } } }; const handleCustomScheme = (url: string) => { // ... existing custom scheme handling }; const extractCredentialOffer = (encodedOffer: string): string | null => { try { const base64 = encodedOffer .replace(/-/g, "+") .replace(/_/g, "/") .padEnd(encodedOffer.length + ((4 - (encodedOffer.length % 4)) % 4), "="); return atob(base64); } catch (error) { console.error("Failed to decode credential offer:", error); return null; } }; return ( ); } ``` **Step 4: Rebuild native projects** After updating the configuration, regenerate the native projects: ```bash title="Regenerate native projects" yarn expo prebuild --clean ``` **Step 5: Build and test** For iOS, you must test Universal Links on a physical device (not simulator) with a production or ad-hoc build. For Android, install the app and verify App Links as described in the Android tab above. ## Testing your URI scheme implementation [#testing-your-uri-scheme-implementation] After configuring your app to handle different URI schemes, test each implementation to ensure it works correctly. ### Testing with QR codes [#testing-with-qr-codes] Create a Credential offer and generate QR codes for each URI scheme type you support: 1. **Standard scheme**: `openid-credential-offer://?credential_offer=...` 2. **Custom scheme**: `com.yourcompany.wallet://accept/{base64UrlEncodedOffer}` 3. **HTTPS scheme**: `https://yourdomain.com/wallet/accept?offer={base64UrlEncodedOffer}` You can use online QR code generators or create them programmatically for testing. ### Testing with deep links [#testing-with-deep-links] For iOS, use the following command to test deep links on a connected device: ```bash title="Test iOS deep link" xcrun simctl openurl booted "com.yourcompany.wallet://accept/BASE64_ENCODED_OFFER" ``` For Android, use: ```bash title="Test Android deep link" adb shell am start -a android.intent.action.VIEW -d "com.yourcompany.wallet://accept/BASE64_ENCODED_OFFER" ``` ### Testing Universal Links / App Links [#testing-universal-links--app-links] For iOS Universal Links, you must test on a physical device with a production or ad-hoc build. Links opened in Safari should open your app. For Android App Links, use: ```bash title="Test Android App Link" adb shell am start -a android.intent.action.VIEW -d "https://yourdomain.com/wallet/accept?offer=BASE64_ENCODED_OFFER" ``` ## Common issues and troubleshooting [#common-issues-and-troubleshooting] ### App doesn't open when scanning QR code or tapping link [#app-doesnt-open-when-scanning-qr-code-or-tapping-link] **Possible causes:** * URI scheme not properly registered in the app configuration. * For Universal Links/App Links, association files not properly hosted or accessible. * For Universal Links/App Links, domain not added to associated domains / intent filters. **Solutions:** * Verify URL scheme registration in your app configuration. * Test the association file URLs directly in a browser to ensure they're accessible. * Check that the association file content matches your app's bundle ID/package name and certificate. * For iOS, verify Associated Domains capability is enabled and domains are correctly listed. * For Android, verify `android:autoVerify="true"` is set and check verification status with `adb shell pm get-app-links`. ### Wrong app opens when multiple wallet apps are installed [#wrong-app-opens-when-multiple-wallet-apps-are-installed] **Possible causes:** * Multiple apps registered for the same URI scheme (common with `openid-credential-offer://`). * Association files not properly verified (for Universal Links/App Links). **Solutions:** * Use a unique custom scheme or domain-verified HTTPS scheme. * For Universal Links/App Links, ensure association files are correctly configured and verified. * Test on a clean device or uninstall competing apps during testing. ### Encoded offer fails to decode [#encoded-offer-fails-to-decode] **Possible causes:** * Incorrect base64 URL encoding/decoding. * Offer parameter not properly extracted from URL. **Solutions:** * Ensure you're using base64 URL encoding (not standard base64) with `-` and `_` instead of `+` and `/`. * Verify padding is handled correctly when decoding. * Log the encoded and decoded values to debug the transformation. ## Related resources [#related-resources] * [iOS Universal Links documentation](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) * [Android App Links documentation](https://developer.android.com/training/app-links) * [OAuth 2.0 for Native Apps (RFC 8252)](https://datatracker.ietf.org/doc/html/rfc8252) # How to implement mDocs revocation status checks in your holding application URL: /docs/holding/credential-claiming-guides/revocation-status-check ## Overview [#overview] This guide demonstrates how to implement revocation status checks for mDocs in your wallet applications. By implementing status checks, your wallet can verify whether credentials have been revoked or remain valid before displaying or presenting them. MATTR's implementation of mDocs revocation is based on Draft 14 of the IETF [Token Status List](https://drafts.oauth.net/draft-ietf-oauth-status-list/draft-ietf-oauth-status-list.html) specification. A revocable mDoc includes a reference to a status list which is managed by the issuer. The list contains the revocation status of multiple credentials, and each credential references the index of its status within a specific status list. Status lists are automatically created and managed by the issuer's MATTR VII tenant when issuing revocable mDocs. They are publicly available and can be consumed by holder applications to check the status of claimed mDocs. For detailed information about how mDocs revocation works, including status list structure, tokens, and signing, see the [Revocation documentation](/docs/issuance/revocation/overview#mdocs). ## Prerequisites [#prerequisites] This guide builds on knowledge from the [Credential Claiming tutorial](/docs/holding/credential-claiming-tutorial). It is recommended to complete that tutorial first, then return here to learn how to implement status checks. ## Understanding revocation status [#understanding-revocation-status] ### Status values [#status-values] Revocable mDocs can have one of the following status values: * **Valid**: The mDoc is valid and can be presented. * **Invalid**: The mDoc is permanently revoked.- **Suspended**: The mDoc is temporarily revoked. * **Unknown**: The status cannot be determined, typically because the status list has expired or is unavailable (e.g. holder is offline). ### How status information is stored [#how-status-information-is-stored] When a revocable mDoc is issued, it includes a `status` object in its MSO payload that references a status list: ```json title="mDoc status reference" // Rest of mDoc payload "status": { "status_list": { "uri": "https://learn.vii.au01.mattr.global/v2/credentials/mobile/status-lists/f331c9be-f526-4577-bbac-ae93d6228f7a/token", "idx": 0 } } ``` * `status_list`: References the status list that holds the status information for this mDoc. * `uri`: The publicly available endpoint where the status list token can be retrieved. * `idx`: The index of this mDoc's status within the referenced status list. When a verifier or holder retrieves a status list, the issuer cannot tell what specific mDoc they are checking the status for. This preserves holder privacy - the issuer does not know how often or to whom an mDoc is being presented. ## Status list caching and updates [#status-list-caching-and-updates] When retrieving a status list, the response is a signed status list token (a [CBOR Web Token](https://datatracker.ietf.org/doc/html/rfc8392)) that includes: * `iat`: Timestamp when the status list token was signed. * `exp`: Expiry timestamp. * `ttl`: Recommended duration in seconds before fetching a new token. * `status_list`: The compressed status list containing the status of all mDocs included in this list. Retrieving a status list for every credential operation would create performance issues and make offline presentation impossible. To address this, MATTR uses a caching mechanism based on the `ttl` and `exp` values: * After retrieving a status list, the SDK will not fetch it again until the `ttl` has passed, as there are unlikely to be any changes. This optimizes performance and reduces unnecessary network requests. * If the SDK fails to retrieve an updated status list **after the TTL** (for example, because the device is offline), it can continue using the cached status until the status list token expiry date (`exp`). * If the **expiry date** passes without a successful update, the credential status can no longer be trusted and it is changed to `Unknown`. It is then up to the application to decide how to handle credentials with `Unknown` status. ### When TTL and EXP changes take effect [#when-ttl-and-exp-changes-take-effect] If the issuer updates the status list TTL and/or EXP settings, the new values apply to the entire status list the next time it is generated and signed. When the holder SDK next retrieves that status list, it will receive the updated token with the new TTL and EXP values. Until then, the SDK continues to use the previously cached status list with its original TTL and EXP values. **Example scenario**: * Credential is issued with a 24-hour TTL (default). * The TTL configuration is changed to 2 hours by the issuer. * The Holder's SDK will continue to use the cached status list with the original 24-hour TTL. * The SDK will only receive the updated 2-hour TTL after it fetches a newly signed status list. ## Implementing status checks [#implementing-status-checks] The Holder SDK **always checks the revocation status** for credentials that support revocation. This cannot be disabled. When calling the `getCredential` method, you can control whether the SDK should attempt to retrieve an updated status list online using the `fetchUpdatedStatusList` parameter: ```swift title="Controlling status list updates" // Retrieve updated status list online (default behavior) let credential = try await mobileCredentialHolder.getCredential( credentialId: credentialId, fetchUpdatedStatusList: true ) // Use valid cached status list only (skip online update) let credential = try await mobileCredentialHolder.getCredential( credentialId: credentialId, fetchUpdatedStatusList: false ) ``` ```kotlin title="Controlling status list updates" // Retrieve updated status list online (default behavior) val credential = mobileCredentialHolder.getCredential( credentialId = credentialId, fetchUpdatedStatusList = true ) // Use valid cached status list only (skip online update) val credential = mobileCredentialHolder.getCredential( credentialId = credentialId, fetchUpdatedStatusList = false ) ``` ```tsx title="Controlling status list updates" // Retrieve updated status list online (default behavior) const credentialResult = await mobileCredentialHolder.getCredential( credentialId, { fetchUpdatedStatusList: true } ) if (credentialResult.isErr()) { const { error } = credentialResult // handle error scenarios return } const credential = credentialResult.value // Use valid cached status list only (skip online update) const credentialResult = await mobileCredentialHolder.getCredential( credentialId, { fetchUpdatedStatusList: false } ) if (credentialResult.isErr()) { const { error } = credentialResult // handle error scenarios return } const credential = credentialResult.value ``` **Important**: Setting `fetchUpdatedStatusList: false` does not disable status checking. The SDK will still check the credential's status using the cached status list (as long as it is valid). This parameter only controls whether the SDK attempts to fetch an updated status list online. ### Status check flow [#status-check-flow] 1. When `getCredential` is called, the SDK checks the `fetchUpdatedStatusList` parameter. 2. **If `fetchUpdatedStatusList` is `true`** (default), the SDK checks whether the currently cached status list has passed its TTL: * If the TTL has **not** passed, the cached status list can be used. The SDK skips the online fetch and proceeds to step 3. * If the TTL **has** passed, the cached status list cannot be used. The SDK attempts to fetch an updated copy of the status list: * If the fetch **succeeds**, the retrieved status list is stored by the SDK as the new cached status list. * If the fetch **fails** (e.g., device is offline), no changes are made to the existing cached status list, and the SDK falls through to step 3. 3. **The SDK evaluates the cached status list**: * If a valid (non-expired) cached status list is **available**, the SDK will use it to check the credential's status and return it. * If the cached status list has **expired** (e.g. EXP has passed) it cannot be relied upon, and the SDK will return the status as `Unknown`. * If **no cached status list exists** (e.g., the status list was never successfully retrieved), the SDK will return the status as `Unknown`. ## Checking credential status [#checking-credential-status] After retrieving a credential, you can check its revocation status to determine whether it should be displayed or presented based on verification failure types: ```swift title="Checking credential status" let credential = try await mobileCredentialHolder.getCredential( credentialId: credentialId ) if let verificationResult = credential.verificationResult, verificationResult.verified { print("Credential is valid and can be presented") // Display credential and allow presentation } else { switch credential.verificationResult?.failureType { case .statusRevoked: print("Credential has been permanently revoked") // Display warning, prevent presentation case .statusSuspended: // Deprecated: only applies to legacy credentials issued before Draft 14 of the Token Status List specification print("Credential has been temporarily suspended") // Display warning, prevent presentation case .statusUnknown: print("Credential status cannot be determined") // Display warning, handle according to your security requirements default: // Handle other failure types break } } ``` ```kotlin title="Checking credential status" val verificationResult = credential.verificationResult if (verificationResult?.verified == true) { Log.d("Tag", "Credential is valid.") } else { when (verificationResult?.failureType) { MobileCredentialVerificationFailureType.StatusRevoked -> { Log.d("Tag", "Credential has been revoked.") // Display warning, prevent presentation } // Deprecated: StatusSuspended only applies to legacy credentials issued before Draft 14 of the Token Status List specification MobileCredentialVerificationFailureType.StatusSuspended -> { Log.d("Tag", "Credential has been suspended.") // Display warning, prevent presentation } MobileCredentialVerificationFailureType.StatusUnknown -> { Log.d("Tag", "Credential status is unknown.") // Display warning, handle according to your security requirements } else -> { // Handle other failure types } } } ``` ```tsx title="Checking credential status" const credentialResult = await mobileCredentialHolder.getCredential( credentialId ) if (credentialResult.isErr()) { const { error } = credentialResult // handle error scenarios return } const credential = credentialResult.value const verificationResult = credential.verificationResult if (verificationResult?.verified === true) { console.log("Credential is valid and can be presented") // Display credential and allow presentation } else { switch (verificationResult?.failureType) { case MobileCredentialVerificationFailureType.StatusRevoked: console.log("Credential has been permanently revoked") // Display warning, prevent presentation break // Deprecated: StatusSuspended only applies to legacy credentials issued before Draft 14 of the Token Status List specification case MobileCredentialVerificationFailureType.StatusSuspended: console.log("Credential has been temporarily suspended") // Display warning, prevent presentation break case MobileCredentialVerificationFailureType.StatusUnknown: console.log("Credential status cannot be determined") // Display warning, handle according to your security requirements break default: // Handle other failure types break } } ``` ## Handling offline scenarios [#handling-offline-scenarios] * **Cache reliance**: When offline, the SDK relies on cached status lists. * **Cached status after TTL**: If the device is offline and `ttl` has passed (but expiry hasn't), the cached status will be used. * **Unknown status after expiry**: If the device is offline and `exp` has passed, the status will always be returned as `Unknown`. * **Unknown status handling**: Define your application's policy for handling credentials with `Unknown` status. Options include: * Preventing presentation. * Allowing presentation with a warning. * Allowing presentation only for recently checked credentials. * **User communication**: Clearly inform users when status checks fail and what it means for their credentials. # Wallet Attestation URL: /docs/holding/credential-claiming-guides/wallet-attestation Description: Learn how Wallet Attestation works in MATTR Holder SDKs to enable credential claiming from issuers that require trusted wallet applications. ## Overview [#overview] Wallet Attestation lets your holder application prove it is a trusted, verified wallet so that issuers who require it will release credentials to you. When an issuer requires Wallet Attestation, your application must cryptographically prove its authenticity before it can claim credentials. Your wallet is identified to issuers using a client identifier (`clientId`) you agree on with each issuer, which lets them recognise your wallet as an approved, trusted origin on their curated list. The Holder SDK automatically generates, manages, and presents the Wallet Attestation proof whenever a credential request occurs, so supporting this capability adds no extra code to your claiming flow and no extra friction for your users — everything happens in the background, and credential claiming looks and feels the same as before. The main work is the one-time coordination with each issuer to establish trust, described in [Setting up Wallet Attestation](#setting-up-wallet-attestation). This mechanism is valuable when: * **Enforcing wallet policies**: Issuers can ensure that only authorized and trusted wallet applications receive their credentials. * **Protecting against unauthorized access**: Credential theft is prevented by verifying the wallet's identity before issuance. * **Leveraging platform-based attestation**: Platform-specific security mechanisms (such as iOS App Attestation and Android Key Attestation) are used to authenticate app instances with the MATTR VII tenant, ensuring that only trusted wallet instances receive attestation proofs for use with issuers. For a detailed technical breakdown of Wallet Attestation from an issuer's perspective, including attestation modes, JWT structures, and certificate trust chains, see [Wallet Attestation](/docs/issuance/credential-issuance/wallet-attestation) in the Issuance documentation. ### What this means for your team [#what-this-means-for-your-team] Supporting Wallet Attestation in your wallet involves more than just the integration code: * **For policy makers**: Wallet Attestation is how issuers decide which wallet applications can receive their credentials. Establishing your wallet as a recognised, trusted application — through the root certificate you share and the client identifier you agree on — is what gets your users' credentials released by issuers that enforce these rules. * **For product managers and designers**: Your existing claiming flows and user experiences stay the same. Attestation proofs are securely generated and presented in the background, so users gain the trust benefits without any added friction or change to how they claim credentials. * **For solution architects**: Integrate the Holder SDK with your MATTR VII tenant and wallet identity. Securely manage identities and cryptographic keys, and design for multi-tenant separation, high availability, observability, error handling, and monitoring of attestation outcomes. ## Standards and specifications [#standards-and-specifications] Wallet Attestation in MATTR VII is based on the following standards: * [OAuth 2.0 Attestation-Based Client Authentication](https://datatracker.ietf.org/doc/draft-ietf-oauth-attestation-based-client-auth/): The core specification for Wallet Attestation * [RFC 9449: DPoP (Demonstrating Proof-of-Possession)](https://www.rfc-editor.org/rfc/rfc9449.html): DPoP specification ## How it works [#how-it-works] At a high level, getting your wallet recognised and claiming credentials from an issuer that requires Wallet Attestation involves four steps: 1. **Share and onboard the root certificate**: You create a Holder root CA certificate on your MATTR VII tenant — this is your Wallet Attestation root — and share it with the issuer, who onboards it as the trust anchor for your wallet application. This is a one-time, out-of-band setup per issuer. 2. **Configure and operate**: The issuer configures their issuance flows to require Wallet Attestation for your wallet, registered against the client identifier you agree on. 3. **Retrieve a credential as usual**: When a user claims a credential, the Holder SDK automatically obtains or refreshes a Wallet Attestation proof — bound to that specific wallet instance — and sends it to the issuer as part of the standard credential retrieval flow. 4. **Verify and issue**: The issuer validates the proof against your onboarded root CA certificate. If it is valid and your wallet is approved, the credential is issued and stored in the app; otherwise the SDK surfaces an error. Steps 1 and 2 are the one-time coordination covered in [Setting up Wallet Attestation](#setting-up-wallet-attestation). Steps 3 and 4 are handled automatically by the SDK at claim time and require no additional code from you. The rest of this section explains the underlying interaction in detail. High-level overview of the Wallet Attestation flow between the holder's MATTR VII tenant, the wallet app, and the issuer's MATTR VII tenant The following diagram illustrates the interaction between the Holder SDK, the MATTR VII tenant, and the issuer. At the protocol level, the Wallet Attestation proof is carried as an OAuth **client attestation JWT** (defined in [OAuth 2.0 Attestation-Based Client Authentication](https://datatracker.ietf.org/doc/draft-ietf-oauth-attestation-based-client-auth/)) combined with a **DPoP proof**. In the descriptions below, "client attestation JWT" refers to the token that conveys the Wallet Attestation to the issuer. 1. **Retrieve the credential offer**: The Holder SDK retrieves the [credential offer](/docs/issuance/credential-offer/overview) from the issuer. This offer includes a reference to the issuer's metadata endpoint. 2. **Check issuer metadata for attestation requirements**: The Holder SDK fetches the issuer's metadata. If the metadata specifies that the issuer requires Wallet Attestation to issue credentials, the SDK proceeds with the attestation flow. If not, credential claiming continues without attestation. 3. **Request a Wallet Attestation token from the tethered tenant**: The Holder SDK automatically makes a request to the MATTR VII tenant it is tethered to, passing the identifiers of the Holder Application configuration that were declared during SDK initialization. 4. **The MATTR VII tenant validates and returns the Wallet Attestation token**: The MATTR VII tenant recognizes the app as a registered instance of the configured Holder Application. Assuming SDK Tethering is correctly set up, the tenant returns a signed client attestation JWT that carries the Wallet Attestation proof. 5. **Generate a DPoP proof and present attestation to the issuer**: The Holder SDK generates a DPoP proof locally and includes both the client attestation JWT and the DPoP proof when requesting the credential from the issuer. Together these two values form the complete Wallet Attestation presented to the issuer. No additional code is required from the holder application developer. 6. **Issuer validates and issues the credential**: The issuer validates the Wallet Attestation (client attestation JWT + DPoP proof), confirms that all requirements are met, and issues the credential to the holder application. ## Setting up Wallet Attestation [#setting-up-wallet-attestation] Complete the following steps to enable Wallet Attestation in your holder application. ### Configure SDK Tethering [#configure-sdk-tethering] Set up [SDK Tethering](/docs/holding/sdk-operations/sdk-tethering) to connect the Holder SDK to your MATTR VII tenant: 1. **Create Holder Applications** on your MATTR VII tenant for each platform target (iOS and Android). 2. **Initialize the SDK** with the correct `platformConfiguration` including the tenant URL and the identifiers of the Holder Application you created. ### Create and activate a Holder root CA certificate [#create-and-activate-a-holder-root-ca-certificate] Your tethered MATTR VII tenant must have an active Holder root CA certificate. This root CA is used to sign Wallet Attestation signers, which in turn sign the client attestation JWTs presented to issuers, forming a [chain of trust](/docs/holding/certificates/overview). MATTR VII supports two types of Holder root CA certificates: * **Managed**: MATTR VII generates and manages the root certificate and its private key on your behalf. Wallet attestation signers are auto-provisioned when needed. * **Unmanaged (external)**: You supply your own root CA in PEM format. You are responsible for signing and uploading wallet attestation signer certificates. For step-by-step instructions on creating and activating a Holder root CA, see the [Holder certificates guide](/docs/holding/certificates/guide). ### Coordinate with the issuer [#coordinate-with-the-issuer] Wallet Attestation requires an out-of-band coordination process between you and each credential issuer. You must complete the following before your wallet can claim credentials from issuers that require Wallet Attestation: 1. **Confirm the issuer's requirements**: Verify with the issuer whether Wallet Attestation is required. See [Understanding issuer requirements](#understanding-issuer-requirements) for details on how issuer-side configuration works. 2. **Share your Holder root CA certificate**: Provide the **public certificate** of your root CA to the issuer (for example, via a secure channel or as part of a business onboarding process). The issuer uses this certificate so they can validate the attestation proof chain your SDK presents. 3. **Align on the client identifier**: The `clientId` you configure on your Holder Application is included as the `sub` claim in the wallet attestation JWT and is the value the issuer sees when your wallet claims a credential. The issuer must have this exact value registered against your wallet (with Wallet Attestation required) in their trusted wallet list. If the value your wallet presents and the value the issuer has registered do not match, the issuer rejects the attestation proof and credential claiming fails. See [Agreeing on a client identifier](#agreeing-on-a-client-identifier) for how to coordinate this value and why agreeing on it early matters. ## Agreeing on a client identifier [#agreeing-on-a-client-identifier] The Wallet Attestation proof already tells the issuer which trusted wallet application is requesting the credential. That proof is what cryptographically establishes your wallet's authenticity. The `clientId` carries no additional product capability on top of this; it exists because the underlying [OAuth 2.0 Attestation-Based Client Authentication](https://datatracker.ietf.org/doc/draft-ietf-oauth-attestation-based-client-auth/) specification requires every client to identify itself with a `client_id`. MATTR VII supports it so your wallet interoperates with any spec-compliant issuer, but the choice of value is something you and each issuer must agree on out of band. There are two ways this coordination plays out: * **You define the client identifier (recommended)**: You choose a single `clientId` for your wallet application (for example, `mattr-wallet`), configure it on your Holder Application, and share it, together with your [Holder root CA certificate](#create-and-activate-a-holder-root-ca-certificate), with each issuer during onboarding. Every issuer registers that same value, so your wallet presents one consistent identity everywhere. This keeps your integration simple: one Holder Application, one `clientId`, and no per-issuer bookkeeping. * **The issuer assigns the client identifier**: Some issuers insist on assigning their own value (for example, one issuer registers your wallet as `mattr-wallet` while another requires `partner-wallet-42`). Because the value your wallet presents must match what each issuer has registered, supporting issuer-assigned identifiers means maintaining a **separate Holder Application for each distinct `clientId`** and tracking which issuer expects which value. This adds operational overhead and is best avoided where possible. **Recommendation**: Wherever you can, agree on a single `clientId` that **you** define and ask each issuer to register it as-is. Raising this early in onboarding, before the issuer has provisioned your wallet in their trusted wallet list, saves you from managing multiple Holder Applications and per-issuer client identifier mappings later. **Important for existing integrations**: If you already have a `client_id` registered with an issuer from a pre-Wallet Attestation integration, we recommend registering a **new `client_id`** for your Wallet Attestation-enabled app rather than reusing the existing one. See [Migrating to Wallet Attestation](#migrating-to-wallet-attestation) for details. Once these steps are complete, the SDK handles all Wallet Attestation interactions automatically whenever an issuer requires it during credential claiming. ## Understanding issuer requirements [#understanding-issuer-requirements] Whether an issuer requires Wallet Attestation from your wallet depends on how they have configured their trusted wallets, not just on what their public metadata advertises. * **Issuer metadata** (the OID4VCI [`.well-known` endpoint](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-issuer-metadata-)) declares which authentication methods the issuer **supports** at a tenant level. For example, the issuer may advertise both `attest_jwt_client_auth_dpop` and `none` as supported methods. * **Per-client configuration** determines what is **required** for any specific wallet. The issuer configures this in their trusted wallet list, associating a client identifier with a specific authentication method. Your wallet cannot determine from public metadata alone whether Wallet Attestation is required for your specific wallet application. The issuer communicates this requirement to you as part of the onboarding process. In practice: * If the issuer has configured your application with Wallet Attestation required, the SDK **must** present a valid attestation proof or credential claiming will fail. * If the issuer has configured your application without Wallet Attestation, the SDK may still send an attestation proof (if it detects attestation support in the issuer metadata), and the issuer will simply ignore it. * If the issuer does not support Wallet Attestation at all (not advertised in metadata), the SDK does not send an attestation proof. ## Migrating to Wallet Attestation [#migrating-to-wallet-attestation] When you enable Wallet Attestation, your SDK uses a different client authentication method (`attest_jwt_client_auth_dpop` instead of `none`). If older app versions that do not support Wallet Attestation share the same client identifier, and the issuer configures that client identifier to require attestation, those older versions will fail to claim credentials. If your holder application is already integrated with an issuer that **does not** require Wallet Attestation, it is recommended to use a new client identifier to migrate safely without breaking existing app versions, as described in the following steps. ### Register a new client identifier with the issuer [#register-a-new-client-identifier-with-the-issuer] Coordinate with the issuer to register a new client identifier (for example, `my-wallet-client-v2`) that will be associated with Wallet Attestation. The issuer adds this new client identifier to their trusted wallet list with Wallet Attestation required, alongside your existing client identifier which continues to work without attestation. ### Configure your Holder Application with the new client identifier [#configure-your-holder-application-with-the-new-client-identifier] When creating or updating your Holder Application on your MATTR VII tenant, set the `clientId` field to the new value you registered with the issuer: ```json title="Request body" { "name": "My iOS Holder Application", "clientId": "my-wallet-client-v2", // [!code focus] "type": "ios", "bundleId": "com.yourcompany.holderapp", "teamId": "YOUR_APPLE_TEAM_ID", "maxTimeOfflineInSecs": 864000, "appAttest": { "required": true, "environment": "production" } } ``` ### Release the updated app [#release-the-updated-app] Publish the new version of your app that supports Wallet Attestation. This version uses the new client identifier and presents Wallet Attestation proofs to issuers that support it. Existing app versions continue to use the original client identifier and operate without attestation. ### Retire the old client identifier [#retire-the-old-client-identifier] Once you are confident that enough users have migrated to the new app version, coordinate with the issuer to remove the old client identifier from their trusted wallet list. This completes the migration and ensures all active wallet instances use Wallet Attestation. # Getting started with the MATTR GO Hold example app URL: /docs/holding/go-hold/getting-started ### Download [#download] Download the MATTR GO Hold example app to your mobile device from: * The [App Store](https://apps.apple.com/us/app/mattr-wallet/id1518660243) for iOS devices. * [Google Play](https://play.google.com/store/apps/details?id=global.mattr.wallet) for Android devices. ## Unlock [#unlock] Once you open the app, you will need to unlock it. The app uses your device configured authentication method to unlock. This can be either a passcode or biometric authentication. Once the app is unlocked, you can proceed with completing the onboarding instructions. You will need to use your device unlock method every time you access the app and for any interactions that require authentication. ## Setup [#setup] You will be prompted to enable push notifications, as the app can notify you when new credentials are available or when the status of an existing credential has changed. If you choose not to enable push notifications during onboarding, you can do so later by accessing the **Settings** menu and setting *Notifications* to `On` under *General*. ## Claim a credential [#claim-a-credential] Perform the following steps to claim a non-production [mDoc](/docs/concepts/mdocs) into your example app: 1. Tap the Blue **Share** button. 2. Select **Respond or Collect**. This will open the camera view (You may need to allow the app to access your camera). 3. Scan the following QR code: QR Code 4. Follow the on-screen instructions to claim the credential. Note that this workflow requires an active internet connection. ## Present a credential online [#present-a-credential-online] Experience presenting a digital credential online using your GO Hold example app by visiting our [Demo hub](https://mattr.global/demo-hub#demos). Follow the guided steps to see how easy it is to share your credentials securely in a real-world scenario. For the best experience, explore both cross-device (scan a QR code from your phone) and same-device (open the link directly on your phone) flows. ## Explore [#explore] MATTR GO Hold example app Explore the different app functionalities: * **Wallet tab**: Shows all credentials that are available in your wallet. It also enables sorting and filtering the list using the icon on the top-right corner. * **Activity tab**: Shows the complete activity history for your wallet. It also enables filtering the displayed events using the icon on the top-right corner. * **Settings tab**: Includes different app settings. * **Interaction button**: Used to claim new credentials or share existing ones by choosing one of the following options: * **Share Credential**: Enables selecting an mDoc and generating a QR code to present it to a verifier via a [proximity presentation flow](/docs/verification/in-person-overview) as per ISO/IEC 18013-5. * **Respond or Collect**: Enables scanning a QR code to claim a credential via an [OID4VCI](/docs/issuance/oid4vci-overview) workflow. ## Dive deeper [#dive-deeper] * For issuers: * [OID4VCI Authorization Code tutorial](/docs/issuance/authorization-code/tutorial): Use the MATTR GO Hold app to claim an mDoc via an OID4VCI Authorization Code flow. * [OID4VCI Pre-authorized Code tutorial](/docs/issuance/pre-authorized-code/tutorial): Use the MATTR GO Hold app to claim an mDoc via an OID4VCI Pre-authorized Code flow. * For verifiers: * [Remote web verification tutorial](/docs/verification/remote-web-verifiers/tutorial): Use the MATTR GO Hold app to present a credential remotely to a web application. * [Remote mobile verification tutorial](/docs/verification/remote-mobile-verifiers/tutorial): Use the MATTR GO Hold app to present a credential remotely to a mobile application. * Download the [MATTR GO Verify](/docs/verification/go-verify/getting-started) example app on a different mobile device and verify credentials stored on your GO Hold example app via a [proximity verification flow](/docs/verification/in-person-overview). # Libraries in use URL: /docs/holding/go-hold/libraries The following lists all notices for third party software that may be used in some way by MATTR GO Wallets, the MATTR GO Hold example app and other development tools. We value the contributions by open source developers and thank them. # System requirements URL: /docs/holding/go-hold/system-requirements Description: Minimum requirements and supported devices for the MATTR GO Hold example app. ## Operating systems [#operating-systems] The following operating system versions represent the minimum supported platforms for MATTR GO Hold. * iOS 15 or higher * Android 7 (API level 24) or higher MATTR validates functionality using currently supported operating system releases provided by Apple and Google. Compatibility with manufacturer-specific Android variants may vary depending on device implementation. For optimal security and performance, devices should run the latest available operating system updates. ## Required device permissions and configuration [#required-device-permissions-and-configuration] Certain device permissions and user settings are required for the application to function correctly, including but not limited to the following: * Enabling camera access: The application requires access to the device camera for credential claiming and presentation flows. If camera access is disabled, these flows will not function. * Enabling biometrics: Some application security features rely on biometric authentication. If biometrics are not enabled on the device, these features will not be available. * Device text size and font scaling: Extreme text size or font scaling settings may affect layout, readability, or visibility of on-screen controls. Users should configure these settings to meet their accessibility needs while still allowing the app interface to display correctly. * Connectivity and settings: Some features require access to the public internet. Users must ensure a stable connection is available. ## Tested devices [#tested-devices] Mobile wallet functionality relies on several device capabilities, including: * Camera access for QR and credential exchange flows * Secure storage and hardware-backed key protection * Biometric authentication * Reliable network connectivity Variations in hardware, operating system customization, and manufacturer implementations can affect these capabilities. For this reason, MATTR verifies functionality only on the devices listed below. Devices not listed here are not verified by MATTR and functionality or performance on those devices is not guaranteed. ### iOS [#ios] * iPhone 15 Pro * iPhone 15 * iPhone 14 Plus * iPhone 14 * iPhone 12 * iPhone 11 Pro * iPhone XR * iPhone XS * iPhone SE ### Android [#android] #### Samsung [#samsung] * Samsung Galaxy S24 * Samsung Galaxy A55 * Samsung Galaxy S23 FE * Samsung Galaxy S22 * Samsung Galaxy S20 FE 5G * Samsung Galaxy Note20 5G #### Google [#google] * Google Pixel 7 Pro * Google Pixel 7 * Google Pixel 6 Pro MATTR may update the list of verified devices and supported operating system versions as new devices and platform updates are released. Customers should ensure their environments remain aligned with the current requirements. # OID4VCI Authorization Code flow journey pattern URL: /docs/holding/credential-claiming-journey-patterns/authorization-code-journey-pattern This journey pattern assumes that the wallet is claiming a credential from an issuer using MATTR VII. However, the same pattern can be applied to any OID4VCI-compliant issuer. This journey pattern is used to issue credentials of different formats to a holder via the OID4VCI [Authorization Code](/docs/issuance/authorization-code/overview) flow. ## Overview [#overview] * **Issuance channel**: Remote, Unsupervised * **Device/s**: On-device / Cross-device / in-person * **Formats**: mDocs, CWT, Semantic CWT * **Information assurance level**: High * **Identity assurance level**: High (when identity assurance checks are included) ## Journey flow [#journey-flow] OID4VCI Authorization Code journey pattern ## Architecture [#architecture] OID4VCI architecture ### Scanning the QR code [#scanning-the-qr-code] The QR code that is used to initiate the issuance workflow is created by the Issuer, but the Holder selects when to scan it using their digital wallet (1) which triggers the issuance workflow. This QR code can be sent to the holder via any existing communication channels, including digital and paper based channels. ### Credential offer [#credential-offer] Once the QR code is scanned it will result in the wallet displaying the credential offer that was created by the Issuer using MATTR VII issuance capabilities. The offer details the credential formats that will be issued in this workflow, and what details would each credential include. Digital Trust Service capabilities (9) enable creating and maintaining policies that define what Issuers can be trusted and what credential types they are allowed to issue. This introduces an additional level of trust to interactions within the trust network, making it easier for Samantha to decide whether or not she wishes to claim a credential from this Issuer. ### Obtaining a binding attribute [#obtaining-a-binding-attribute] The OpenID Credential Provisioning (2) component commences the credential issuance flow by obtaining a unique binding attribute from the requesting device/wallet. This happens when the user accepts the credential offer (step 3 in the pattern above). The binding attribute is carried through the proceeding steps to bind the intended credential holder and the data. ### Authentication [#authentication] The OpenID Connect Provider (IdP) is a component managed by the Issuer to authenticate users against the data they hold about them. The Issuer’s IdP (3) facilitates the authentication flow (step 4 in the pattern above) which may include multiple steps that test different factors such as: * Basic username/password authentication. * Proving device ownership through acknowledging a unique request sent directly to the device. * Providing genuine presence assurance (liveness check) that the correct user is in possession of the device being used to facilitate the journey. The issuer can configure this authentication flow requests to include login hint parameters (for example pre populating the user e-mail address) to create more seamless user experiences. ### Interaction hook [#interaction-hook] Following successful authentication, the user can be redirected to a custom component (4) hosted or controlled by the Issuer (step 5 in the pattern above). This custom component can initiate additional steps for the user to perform as part of issuing the credential. This can be used to embed enrolment within the issuance journey. We refer to these custom components as *Interaction hooks*. ### Claims source [#claims-source] As part of gathering authenticated claims that are included in the issued credential, the Issuer may want to retrieve additional information about the user from external data store/s (5). This optional step (step 6 in the pattern above) enables issuing credentials that are more rich in information and thus provide greater value. It is achieved by configuring an external data store, which we refer to as a *Claims source*. ### Credential generation [#credential-generation] The information then gets passed back through the OpenID Credential Provisioning component (2) to map against an established vocabulary, and express the intended context around each piece of information it holds. The mapped data is then passed to the Credential Generation component (6) which formats, binds and signs the data into a credential that is ready to be sent to the requesting wallet/device (step 7 in the pattern above). The credential can be issued in multiple-formats. Additional features may be enabled to support capabilities such as credential revocation or allowing the holder to respond to selective-disclosure verification requests. ### Credential management [#credential-management] Digital wallets (1) can be used to manage the acceptance and secure storage of the credential on the Holder’s device upon completion of the credential issuance flow (step 8 in the pattern above). This can be achieved by wallets built with our MATTR Pi Wallet SDK or branded MATTR GO Hold applications. ### Webhooks [#webhooks] At the completion of the issuance flow, MATTR VII will trigger any configured Webhook events to configured recipients (7). These events (step 9 in the pattern above) offer additional information about the credential issuance (such as the wallet DID) back to the issuer for them to utilize or store. This enables integrating issuance workflows into existing business processes, or creating new ones based on this capability. # OID4VCI Pre-authorized Code flow journey pattern URL: /docs/holding/credential-claiming-journey-patterns/pre-authorized-code-journey-pattern This journey pattern assumes that the wallet is claiming a credential from an issuer using MATTR VII. However, the same pattern can be applied to any OID4VCI-compliant issuer. This journey pattern is used to issue credentials of different formats to a holder via the OID4VCI [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview) protocol. ## Overview [#overview] * **Issuance channel**: Remote, Unsupervised * **Device/s**: On-device / Cross-device / in-person * **Formats**: mDocs * **Information assurance level**: High * **Identity assurance level**: High (depends on the mechanism used by the issuer to authenticate the holder prior to sharing a credential offer) ## Journey flow [#journey-flow] OID4VCI Pre-authorized Code journey pattern ## Architecture [#architecture] OID4VCI architecture ### Logging into a provider's portal [#logging-into-a-providers-portal] The underlying architecture assumes that the Issuer can reliably confirm the user's identity before issuing the credential offer. This enables seamless, low-friction issuance experiences while preserving trust and security. ### Scanning the QR code [#scanning-the-qr-code] The QR code used to initiate the issuance workflow is generated by the Issuer, while the Holder controls when to scan it using their digital wallet. Scanning the QR code triggers the credential issuance process. ### Credential offer [#credential-offer] Once the QR code is scanned it will result in the wallet displaying the credential offer that was created by the Issuer using MATTR VII issuance capabilities. When the QR code is scanned, the Holder’s digital wallet initiates the credential issuance workflow and displays the credential offer prepared by the Issuer using MATTR VII's issuance capabilities. The offer outlines the credential formats to be issued and specifies the claims included in each credential. In a pre-authorized flow, the Issuer can gather information about the intended Holder in advance—since the offer is created for a known user—allowing the Issuer to tailor the credential with specific, user-relevant claims. Digital trust service capabilities enable creating and maintaining policies that define what Issuers can be trusted and what credential types they are allowed to issue. This introduces an additional level of trust to interactions within the trust network, making it easier for Samantha to decide whether or not she wishes to claim a credential from this Issuer. ### Obtaining a binding attribute [#obtaining-a-binding-attribute] The OpenID Credential Provisioning component commences the credential issuance flow by obtaining a unique binding attribute from the requesting device/wallet. This happens when the user accepts the credential offer. The binding attribute is carried through the proceeding steps to bind the intended credential holder and the data. ### Transaction code [#transaction-code] To enhance the security of the pre-authorized issuance flow, the Issuer may optionally require the Holder to provide a transaction code before the credential can be claimed. This code is generated by the Issuer as part of the credential offer and is uniquely associated with that offer. The Issuer sends the transaction code to the user through a secure, alternative communication channel such as email or SMS. When the user initiates the issuance process by scanning the QR code, they are prompted by the wallet to enter the transaction code. The credential can only be issued if the correct code is supplied. This optional verification step strengthens the assurance that the credential is issued to the intended recipient, helping to prevent unauthorized access in scenarios where the credential offer may have been intercepted or misdirected. ### Credential issuance [#credential-issuance] The information then gets passed back through the OpenID Credential Provisioning component to map against an established vocabulary, and express the intended context around each piece of information it holds. The mapped data is then passed to the Credential Generation component which formats, binds and signs the data into a credential that is ready to be sent to the requesting wallet/device. ### Credential management [#credential-management] Digital wallets can be used to manage the acceptance and secure storage of the credential on the Holder’s device upon completion of the credential issuance flow. This can be achieved by wallets built with our MATTR Pi Wallet SDK or branded MATTR GO Hold applications. # How to build an Android application that can present a credential via the DC API URL: /docs/holding/dc-api/guide-android Digital Credentials (DC) API support is currently offered as a tech preview. The DC API specification itself is still under active development in the W3C Web Incubator CG, and platform implementations continue to evolve. As such, functionality may be limited, may not work in all scenarios, and could change or break without prior notice as browsers and operating systems update their implementations. ## Overview [#overview] This guide demonstrates how to use the [Holder SDK](/docs/holding/sdk-overview) to build an Android application that can present a claimed [mDoc](/docs/concepts/mdocs) to a verifier [remotely](/docs/verification/remote-overview) via the [DC API](/docs/holding/dc-api/overview), as per Annex D of [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html). ## Prerequisites [#prerequisites] ### Application base [#application-base] This guide builds on the knowledge gained in the [Claim a credential](/docs/holding/credential-claiming-tutorial) and [Remote presentation](/docs/holding/remote-presentation-tutorial) tutorials. It is recommended to complete those tutorials first, then return here to add support for the [DC API workflow](/docs/holding/dc-api/workflow). ### Testing Devices [#testing-devices] * Mobile device: * Android device running Android 14 or later. * The device must have a holder application with claimed credentials set up as per the [Claim a credential tutorial](/docs/holding/credential-claiming-tutorial) and support for the DC API enabled as per the instructions in this guide. * Desktop device with a web browser that supports the DC API: * Chrome or a Chromium based browser v138 or later. * Safari v26 or later. ## How it works [#how-it-works] The DC API is a browser standard that enables web applications to request and verify digital credentials directly from compatible wallet applications on the user's device. This provides a seamless user experience where credential presentation happens entirely within the browser context. From a holder application point of view, there are several changes required to support remote presentations via the DC API compared to the standard [OID4VP remote presentation workflow](/docs/verification/remote-web-verifiers/workflow): * When the DC API feature is enabled upon initialization, all claimed credentials are automatically registered with the [Digital Credentials Manager (DCM)](https://developer.android.com/identity/digital-credentials), making them available when a website requests credentials of matching types. * When a user selects to present a credential from the holder application, the SDK handles the entire interaction in the background by: * Receiving the presentation request from the operating system via the DCM. * Locating the requested credential. * Authenticating the user via the operating system's native authentication mechanisms (fingerprint recognition, face recognition, or PIN code as defined for the credential). * Creating and returning the credential presentation to the DCM, which then continues the interaction with the browser without the holder application needing to be directly involved in the process. ## Adjusting your mobile application to support remote presentations via the DC API [#adjusting-your-mobile-application-to-support-remote-presentations-via-the-dc-api] The Holder SDK handles most of the complexity of the DC API workflow internally. You will have to make the following adjustments to enable it in your application: 1. Initialize the SDK with support for the DC API enabled. 2. Handle callbacks for verifier authentication (optional). ### Initialize the SDK with support for the DC API enabled [#initialize-the-sdk-with-support-for-the-dc-api-enabled] When you initialize the SDK with the DC API feature enabled, the SDK automatically: * Registers your application as a credential provider with the Android operating system * Registers all claimed credentials with the Digital Credentials Manager (DCM) * Handles incoming presentation requests Adjust your SDK initialization code to enable DC API support by setting the `enabled` flag to `true` in the `DcmConfiguration`: ```kotlin title="Initializing the SDK with DC API support enabled" MobileCredentialHolder.getInstance().initialize( context, dcmConfiguration = DcmConfiguration(enabled = true) ) ``` ### Handle verifier authentication results (optional) [#handle-verifier-authentication-results-optional] The SDK provides an API to detect if the Verifier can be trusted. This is an optional step as the SDK will handle the presentation flow and user authentication with the operating system in the background without any involvement from the app. However, if your application has specific requirements around when verifier authentication should be required, you can use this API to enforce that. Perform the following steps to implement verifier authentication in your application: 1. Add the following [intent filter](https://developer.android.com/guide/components/intents-filters#Receiving) in your application's `AndroidManifest.xml` file to allow it to receive the verifier authentication result from the SDK: ```xml title="AndroidManifest.xml" ``` These are both **optional** intent filters. The first one allows your application to receive the verifier authentication result when the verifier is trusted or untrusted but signed. The second one allows your application to receive a callback when an error occurs during the authentication process. If you choose not to implement these intent filters, the SDK will still handle the presentation flow and user authentication with the operating system in the background without any involvement from your app. 2. Handle the incoming intents in the corresponding activity `onCreate` method: ```kotlin title="Handling verifier confirmation and errors" val verifierAuthenticationResult: VerifierAuthenticationResult? = when (intent.action) { "global.mattr.credentialmanager.action.CONFIRMATION" -> { IntentCompat.getParcelableExtra( intent, "global.mattr.credentialmanager.extra.verifier_authentication_result", VerifierAuthenticationResult::class.java ) } "global.mattr.credentialmanager.action.ERROR" -> null else -> { finish() return } } ``` This code example retrieves the verifier authentication result from the incoming intent when the action is `global.mattr.credentialmanager.action.CONFIRMATION`. If the action is `global.mattr.credentialmanager.action.ERROR`, it sets the result to null, indicating that an error occurred during the authentication process. 3. Handle the result of the Verifier authentication based on your requirements. For example: ```kotlin title="Handling the verifier authentication result" when (verifierAuthenticationResult) { is VerifierAuthenticationResult.Trusted -> { // If this reader is trusted, just return success setResult(RESULT_OK) finish() return } is VerifierAuthenticationResult.Untrusted, is VerifierAuthenticationResult.Unsigned -> { // Require user confirmation and set the activity result based on the // user's decision in the confirmation screen. } null -> { // An error occurred during the authentication process. Show an error message. // ... setResult(RESULT_CANCELED) finish() return } } ``` In this example, if the verifier is trusted, the application simply returns a successful result. If the verifier is untrusted or unsigned, the application can prompt the user for confirmation and set the activity result based on the user's decision. ### Test the application [#test-the-application] Let's test that the application is working as expected in both workflows. #### Same-device workflow [#same-device-workflow] 1. Use a browser on your testing mobile device to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). 2. Select **Digital Credentials API** from the *Select Experience* list. 3. Select **Request credentials**. 4. Confirm interacting with the verifier on your mobile device when prompted by the operating system (this might look different based on the device and operating system version). 5. Select the credential you wish to send to the verifier from the list of credentials suggested by the operating system. 6. Send the response. 7. The UI prompt will close, and you should see a successful verification indication in the browser where you initiated the request. #### Cross-device workflow [#cross-device-workflow] 1. Use a desktop browser to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). 2. Select **Digital Credentials API** from the *Select Experience* list. 3. Select **Request credentials**. 4. Open the camera on your testing device and scan the QR code. 5. Confirm interacting with the verifier on your mobile device when prompted by the operating system (this might look different based on the device and operating system version). 6. Select the credential you wish to send to the verifier from the list of available credentials suggested by the operating system. 7. Send the response. 8. The UI prompt will close. 9. Back on your desktop browser, you should see a successful verification indication. ## Summary [#summary] You have just used the [Android Holder SDK](/docs/holding/sdk-overview) to build an Android application that can present a claimed [mDoc](/docs/concepts/mdocs) to a verifier [remotely](/docs/verification/remote-overview) via the [DC API](https://wicg.github.io/digital-credentials), as per Annex D of [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html). This was achieved by making the following adjustments to your application: 1. Initialize the SDK with support for the DC API enabled. 2. Optionally, handle the verifier's authentication result. ## What's next? [#whats-next] * You can [build a web application](/docs/verification/remote-web-verifiers/dc-api/guide) that will interact with your wallet application via an online presentation workflow using the DC API. * You can build additional capabilities into your new application: * Present a claimed mDoc for verification via a [proximity presentation workflow](/docs/holding/proximity-presentation-tutorial). * You can check out the [SDK reference documentation](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/index.html) for more details on the available functions and classes. # How to build an iOS application that can present a credential via the DC API URL: /docs/holding/dc-api/guide-ios Digital Credentials (DC) API support is currently offered as a tech preview. The DC API specification itself is still under active development in the W3C Web Incubator CG, and platform implementations continue to evolve. As such, functionality may be limited, may not work in all scenarios, and could change or break without prior notice as browsers and operating systems update their implementations. ## Overview [#overview] This guide demonstrates how to use the [iOS Holder SDK](/docs/holding/sdk-overview) to build an iOS application that can present a claimed [mDoc](/docs/concepts/mdocs) to a verifier [remotely](/docs/verification/remote-overview) via the [DC API](/docs/holding/dc-api/overview), as per Annex C of [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html). ## Prerequisites [#prerequisites] ### Application base [#application-base] This guide builds on the knowledge gained in the [Claim a credential](/docs/holding/credential-claiming-tutorial) and [Remote presentation](/docs/holding/remote-presentation-tutorial) tutorials. It is recommended to complete those tutorials first, then return here to add support for the [DC API workflow](/docs/holding/dc-api/workflow). ### Testing Devices [#testing-devices] * Mobile device: * iOS device running iOS 26.0 or later. * The device must have a holder application with claimed credentials set up as per the [Claim a credential tutorial](/docs/holding/credential-claiming-tutorial) and support for the DC API enabled as per the instructions in this guide. * Desktop device with a web browser that supports the DC API: * Chrome or a Chromium based browser v138 or later. * Safari v26 or later. ### Development environment [#development-environment] * Xcode 26.0.0 or later. ## How it works [#how-it-works] The DC API is a browser standard that enables web applications to request and verify digital credentials directly from compatible wallet applications on the user's device. This provides a seamless user experience where credential presentation happens entirely within the browser context. From a holder application point of view, there are several changes required to support remote presentations via the DC API compared to the standard [OID4VP remote presentation workflow](/docs/verification/remote-web-verifiers/workflow): * The holder application must register itself as a credential provider with the iOS operating system, allowing it to be automatically discovered when websites request credentials. * Each claimed credential must be registered individually with the operating system to make it available when a website requests that specific credential type. * When a user selects to present a credential from the holder application, the application isn't invoked directly. Instead, an app extension handles the interaction with the user, retrieving the presentation request and managing the credential presentation process. ## Adjusting your mobile application to support remote presentations via the DC API [#adjusting-your-mobile-application-to-support-remote-presentations-via-the-dc-api] The Holder SDK handles most of the complexity of the DC API workflow internally. You will have to make the following adjustments to enable it in your application: 1. [Configure your XCode project to support the new app extension](#configure-your-xcode-project-to-support-the-new-app-extension). 2. [Create and configure an Identity Document Provider extension](#create-and-configure-an-identity-document-provider-extension). 3. [Initialize the SDK with support for the DC API enabled](#initialize-the-sdk-with-support-for-the-dc-api-enabled). 4. [Handle the presentation request](#handle-the-presentation-request). ### Configure your XCode project to support the new app extension [#configure-your-xcode-project-to-support-the-new-app-extension] On iOS, DC API presentation requests are handled by an [app extension](https://developer.apple.com/documentation/technologyoverviews/app-extensions) rather than your main application. The app extension is a separate target within your Xcode project that provides a lightweight UI for credential selection and user consent. First, configure your Xcode project so the main app can host an Identity Document Provider extension and share secure data with it. **Step 1: Set the minimum deployment target** 1. In Xcode, select your app target → General. 2. Set Minimum Deployments to iOS 26.0 (or later). **Step 2: Enable Keychain Sharing (required for SDK key access)** 1. Select your app target → Signing & Capabilities. 2. Click + Capability → add Keychain Sharing. 3. Add a Keychain Group and make sure you use the same Keychain Group for both the app and the extension. **The Keychain Group name must be the same as your app's `bundleId`.** This is required because the extension must be able to access keys and secrets the SDK stores in the Keychain. The Keychain Sharing group name **must match your app's bundle identifier exactly**, and **must be the same for both the app and the extension**. If the group names differ between targets, or don't match the app's `bundleId`, the SDK may store keys under an incorrect access group, leading to initialization failures that are difficult to diagnose. **Step 3: Enable an App Group entitlement (required for shared storage)** 1. In Signing & Capabilities, click + Capability → add App Groups. 2. Create an App Group identifier (for example: group.com.yourcompany.yourapp). 3. Select the same App Group for both the app and the extension. 4. This allows the app and extension to share data via the App Group container (for example, configuration and supporting state). **Step 4: Register the app as a Digital Credentials provider** 1. In Signing & Capabilities, add the entitlement Digital Credentials API - Mobile Document Provider (This entitlement is only available to apps signed by an Apple Developer Program team. It may not appear for individual accounts). 2. Select all document types your holder will support (or select all during development). This entitlement is required before iOS will allow your app (via its extension) to provide mobile documents using Apple’s identity document services. ### Create and configure an Identity Document Provider extension [#create-and-configure-an-identity-document-provider-extension] Next you will create the app extension that iOS invokes to handle mobile document requests. You’ll also configure the extension’s entitlements so it can share Keychain items and App Group storage with the main app (which is essential for accessing the SDK’s keys and shared configuration). **Step 1: Add the Identity Document Provider target in Xcode** 1. Open your project and select the project (blue icon) in the Project Navigator. 2. Under Targets, click the + button (or use File → New → Target…). 3. Select iOS in the template chooser. 4. Search for *Identity Document Provider*, choose it and click **Next**. 5. Enter the new target details (name, bundle identifier suffix, etc.), then click **Finish**. After Xcode creates the target, it typically opens (or navigates you to) `DocumentProviderExtension.swift`. You should see a scaffold similar to: ```swift title="DocumentProviderExtension.swift" import ExtensionKit import IdentityDocumentServicesUI import SwiftUI @main struct DocumentProviderExtension: IdentityDocumentProvider { var body: some IdentityDocumentRequestScene { ISO18013MobileDocumentRequestScene { context in // Insert your view here Text("Hello, world!") } } func performRegistrationUpdates() async { } } ``` **Step 2: Configure Signing & Capabilities for the extension target** Now you’ll add the same shared entitlements to the extension target so it can access the same Keychain items and App Group container as the main app. 1. Select the project (blue icon) in the Project Navigator. 2. Under Targets, select your new Identity Document Provider target (the extension). 3. Open the Signing & Capabilities tab. 4. Click + Capability → Keychain Sharing. 5. Add the same Keychain Group you configured on the main app target (this must match the app's `bundleId`). 6. Click + Capability → App Groups. 7. Select the same App Group you configured on the main app target. These values **must match exactly** between the app and the extension. If they don’t, the extension won’t be able to read the SDK’s stored keys or shared data. ### Initialize the SDK with support for the DC API enabled [#initialize-the-sdk-with-support-for-the-dc-api-enabled] **Do not call the SDK's initialize function from within the App Extension.** The SDK must only be initialized from the main application target. Calling initialize from the extension can overwrite storage keys and save them under an incorrect Keychain access group, causing initialization failures in the main app that are difficult to recover from. In this step, you’ll: * Define the shared App Group identifier in code (so it can be reused consistently). * Initialize the SDK with a `DCConfiguration`. * Register your stored credentials with the Digital Credentials API, enabling your extension to respond to mdoc presentation requests from Safari and other supported browsers. This is the final wiring step that connects: Main App → Shared Storage → SDK → Digital Credentials API → App Extension **Step 1: Define the shared App Group constant** Open `Constants.swift` in your **main application target** and add the shared App Group identifier: ```swift title="Constants.swift" static let appGroup = "group.com.mattr.identityprovider" ``` The value **must exactly match** the App Group you configured for both the main app and the extension in the previous step. Keeping this in `Constants.swift` ensures you avoid mismatches across targets. **Step 2: Initialize the SDK with DC API support enabled** Open `ContentView.swift` in your **main app target** and update the SDK initialization to include `dcConfiguration`: ```swift title="ContentView.swift" try await mobileCredentialHolder.initialize( userAuthenticationConfiguration: UserAuthenticationConfiguration( userAuthenticationBehavior: .onDeviceKeyAccess ), credentialIssuanceConfiguration: CredentialIssuanceConfiguration( redirectUri: Constants.redirectUri, autoTrustMobileCredentialIaca: true ), dcConfiguration: DCConfiguration( // [!code focus] appGroup: Constants.appGroup, // [!code focus] supportedDocTypes: [.euav, .eudi, .jpMnc, .mDL, .photoid] // [!code focus] ) ) ``` When you initialize the SDK with the dcConfiguration parameter: * The SDK registers all existing credentials with the DC API. * Any future credentials issued to the holder will also be registered automatically. * iOS recognizes your app extension as a valid provider for the configured document types. * The extension becomes available to handle DC API presentation requests from supported browsers. This ensures your holder integrates seamlessly with the system-level DC API framework. **Step 3: Verify your configuration** To confirm everything is set up correctly: 1. Build and run your app on the testing iOS device. 2. On first initialization with `dcConfiguration`, iOS will prompt you to: *Allow this app to be used for identity verification on websites*. 3. Approve the prompt. 4. Navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials) using a supported browser. 5. Select **Digital Credentials API** from the *Select Experience* list. 6. Select **Request credentials**. If everything is configured correctly: * The browser initiates a Digital Credentials request. * iOS launches your Identity Document Provider extension. * You’ll see the extension UI open (currently showing the placeholder view). At this point, your end-to-end integration is complete. Your holder app is now registered as a Digital Credentials provider and ready to respond to verification requests. Next, you’ll implement the logic to handle the presentation request and allow the user to select and present a credential. ### Handle the presentation request in the app extension [#handle-the-presentation-request-in-the-app-extension] In this step, you’ll wire your Identity Document Provider extension into the Holder SDK so it can: * Read credentials from the shared app container (via the App Group). * Create a Digital Credentials presentation session from the system request context. * Show a consent screen where the user selects which credential to share. * Send the response back to the verifier. **Step 1: Add the SDK dependency to the extension target** To import the SDK in the extension, add `MobileCredentialHolderSDK.xcframework` as a dependency: 1. Select your project (blue icon) in the Project Navigator. 2. Under Targets, select your Identity Document Provider extension target. 3. Open General → scroll to Frameworks, Libraries, and Embedded Content. 4. Click `+` and add `MobileCredentialHolderSDK.xcframework` (or add it via Package Dependencies if your project uses Swift Package Manager). **Step 2: Add required shared source files to the extension target** The extension must compile with the same shared identifiers and UI helpers used by the main app. 1. If you haven’t completed the [Remote Presentation](/docs/holding/remote-presentation-tutorial) or [Proximity Presentation](/docs/holding/proximity-presentation-tutorial) tutorials, add the following file named `PresentCredentialsView.swift` to your project and make sure it’s included in the extension target membership: ```swift title="PresentCredentialsView.swift" import MobileCredentialHolderSDK import SwiftUI struct PresentCredentialsView: View { var viewModel: PresentCredentialsViewModel @State var selectedID: String? var body: some View { ScrollView { VStack(alignment: .leading, spacing: 20) { Text("Requested Documents") .font(.headline) .padding(.leading) ForEach(viewModel.requestedDocuments, id: \.docType) { requestedDocument in DocumentView(viewModel: DocumentViewModel(from: requestedDocument)) } Text("Matched Credentials") .font(.headline) .padding(.leading) ForEach(viewModel.matchedMetadata, id: \.id) { matchedMetadata in VStack(alignment: .leading, spacing: 10) { if let matchedCredential = viewModel.matchedMobileCredential(id: matchedMetadata.id) { DocumentView(viewModel: DocumentViewModel(from: matchedCredential)) .padding(.vertical) .background(selectedID == matchedMetadata.id ? Color.blue.opacity(0.2) : Color.clear) .onTapGesture { guard selectedID != matchedMetadata.id else { selectedID = nil return } selectedID = matchedMetadata.id } Button("Hide claim values") { viewModel.matchedCredentials.removeAll(where: { $0.id == matchedMetadata.id }) } .frame(maxWidth: .infinity, alignment: .center) } else { DocumentView(viewModel: DocumentViewModel(from: matchedMetadata)) .padding(.vertical) .background(selectedID == matchedMetadata.id ? Color.blue.opacity(0.2) : Color.clear) .onTapGesture { guard selectedID != matchedMetadata.id else { selectedID = nil return } selectedID = matchedMetadata.id } Button("Show claim values") { viewModel.getCredentialAction(matchedMetadata.id) } .frame(maxWidth: .infinity, alignment: .center) } } } } } if selectedID != nil { Button("Send Response") { viewModel.sendCredentialAction(selectedID!) } .buttonStyle(.borderedProminent) .clipShape(Capsule()) .frame(maxWidth: .infinity, alignment: .center) } } } // MARK: PresentCredentialsViewModel class PresentCredentialsViewModel { @Binding var requestedDocuments: [MobileCredentialRequest] @Binding var matchedCredentials: [MobileCredential] @Binding var matchedMetadata: [MobileCredentialMetadata] var getCredentialAction: (String) -> Void var sendCredentialAction: (String) -> Void init( requestedDocuments: Binding<[MobileCredentialRequest]>, matchedCredentials: Binding<[MobileCredential]>, matchedMetadata: Binding<[MobileCredentialMetadata]>, sendCredentialAction: @escaping (String) -> Void, getCredentialAction: @escaping (String) -> Void ) { self._requestedDocuments = requestedDocuments self._matchedCredentials = matchedCredentials self._matchedMetadata = matchedMetadata self.sendCredentialAction = sendCredentialAction self.getCredentialAction = getCredentialAction } func matchedMobileCredential(id: String) -> MobileCredential? { matchedCredentials.first(where: { $0.id == id }) } } ``` This view: * Shows what the verifier requested. * Lists credentials that match the request. * Lets the user reveal claim values (optional) and consent by tapping **Send Response**. The `PresentCredentialsViewModel` object is used to reference values from a credential request. It takes two closures in its initializer: * `getCredentialAction: (String) -> Void` is used to display claim values. * `sendCredentialAction: (String) -> Void` is used to send a credential response to the verifier once the user selected a credential and provided consent by selecting the **Send Response** button. 2. Select the `Constants.swift` file. 3. Use the File Inspector to enable Target Membership for your app extension. 4. Do the same for `DocumentView.swift` and `PresentCredentialsViewModel.swift` (and any related view models/types it depends on). **Step 3: Implement the extension handler (`DocumentProviderExtension.swift`)** 1. Replace any existing code in `DocumentProviderExtension.swift` with the following code, which will be the basis for handling DC API presentation requests: ```swift title="DocumentProviderExtension.swift" import ExtensionKit import IdentityDocumentServicesUI import SwiftUI // DC API - Step 3.2: Import MobileCredentialHolderSDK @main struct DocumentProviderExtension: IdentityDocumentProvider { var body: some IdentityDocumentRequestScene { ISO18013MobileDocumentRequestScene { context in DocumentProviderView(context: context) } } /// This method is required for conformance to IdentityDocumentProvider. /// However, the SDK manages all credential registrations automatically, /// so no implementation is needed here. func performRegistrationUpdates() async { } } struct DocumentProviderView: View { @State private var viewModel: ExtensionViewModel init(context: ISO18013MobileDocumentRequestContext) { self._viewModel = State(initialValue: ExtensionViewModel(context: context)) } var body: some View { presentCredentialsView // DC API - Step 3.9: Initialize the session when view appears } var presentCredentialsView: some View { // DC API - Step 3.8: Display the credential selection UI EmptyView() } } @Observable final class ExtensionViewModel { // DC API - Step 3.3: Store a reference to the SDK let context: ISO18013MobileDocumentRequestContext // DC API - Step 3.5: Add properties to manage presentation state init(context: ISO18013MobileDocumentRequestContext) { self.context = context // DC API - Step 3.4: Initialize the SDK for app extension } // DC API - Step 3.6: Create DC presentation session from context func createDCSession() { } // DC API - Step 3.7: Retrieve credential from storage @MainActor func getCredential(id: String) { } } ``` 2. Under `// DC API - Step 3.2: Import MobileCredentialHolderSDK`, import the SDK: ```swift title="Importing the SDK in the extension" import MobileCredentialHolderSDK ``` This gives the extension access to `MobileCredentialHolder`, presentation sessions, and credential models. 3. Under `// DC API - Step 3.3: Store a reference to the SDK`, add a `holder` property referencing the `MobileCredentialHolder` singleton: ```swift title="Adding holder reference in the extension view model" let holder = MobileCredentialHolder.shared ``` You’ll use this reference to initialize the SDK, create a presentation session, and fetch credentials. 4. Under `// DC API - Step 3.4: Initialize the SDK for app extension`, initialize the SDK using the shared App Group: ```swift title="Initializing the SDK in the extension" do { try holder.initializeAppExtension(appGroup: Constants.appGroup) } catch { print(error.localizedDescription) } ``` `Constants.appGroup` must be **the same App Group** used in the main app’s `DCConfiguration`. This is what allows the extension to read the same stored credentials and supporting data. 5. Under `// DC API - Step 3.5: Add properties to manage presentation state`, add the properties used by the consent UI and the response flow: ```swift title="Adding presentation state properties to the extension view model" var presentationSession: DCPresentationSession? var matchedCredentials: [MobileCredential] = [] var matchedMetadata: [MobileCredentialMetadata] = [] var credentialRequest: [MobileCredentialRequest] = [] ``` These values drive what the extension displays (requested data + matching credentials) and what it can send back to the verifier: * `presentationSession` holds the active DC presentation session created from the system request. * `matchedCredentials` are the credentials retrieved from storage that match the verifier’s request (used to show claim values in the UI). * `matchedMetadata` is the metadata about the matched credentials (used to show the credential in the UI before revealing claim values). * `credentialRequest` is the original request from the verifier (used to show what was requested and to create the presentation response). 6. Add a new function under `// DC API - Step 3.6: Create DC presentation session from context` to convert the system request context into a `DCPresentationSession` and extract the request details: ```swift title="Creating a DC presentation session from the system request context" func createDCSession() { presentationSession = try? holder.createDcPresentationSession(from: context) matchedMetadata = presentationSession?.request .flatMap { $0.matchedCredentialsMetadata } .compactMap { $0 } ?? [] credentialRequest = presentationSession?.request .compactMap { $0.credentialRequest } .compactMap { $0 } ?? [] } ``` This extracts what the verifier requested (`credentialRequest`) and which stored credentials match it (`matchedMetadata`), then uses these values to populate the consent screen. 7. Add a new function under `// DC API - Step 3.7: Retrieve credential from storage` to load a specific credential by ID when the user wants to reveal claim values: ```swift title="Retrieving a credential from storage in the extension" @MainActor func getCredential(id: String) { Task { do { let credential = try await holder.getCredential(credentialId: id) matchedCredentials.append(credential) } catch { print(error) } } } ``` 8. Under `// DC API - Step 3.8: Display the credential selection UI` replace `EmptyView()` with the following code to show the `PresentCredentialsView` and pass the necessary data and actions: ```swift title="Displaying the credential selection UI in the extension" PresentCredentialsView( viewModel: PresentCredentialsViewModel( requestedDocuments: $viewModel.credentialRequest, matchedCredentials: $viewModel.matchedCredentials, matchedMetadata: $viewModel.matchedMetadata, sendCredentialAction: { credentialID in Task { try? await viewModel.presentationSession?.sendResponse(credentialIDs: [credentialID]) } }, getCredentialAction: viewModel.getCredential(id:) ) ) ``` * `sendCredentialAction` sends the selected credential back to the relying party using DCPresentationSession.sendResponse(...). * `getCredentialAction` loads a specific credential to display claim values before consent. 9. Under `// DC API - Step 3.9: Initialize the session when view appears`, call `createDCSession()` when the view appears to set up the presentation session and populate the UI: ```swift title="Initializing the DC presentation session when the view appears" .task { viewModel.createDCSession() } ``` This ensures the session and request metadata are created as soon as the extension UI is shown, so the consent screen can render immediately. ### Test the application [#test-the-application] Let's test that the application is working as expected in both workflows. #### Same-device workflow [#same-device-workflow] 1. Run the app and then close it (this updates the app on your testing device). 2. Use a browser on your testing mobile device to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). 3. Select **Digital Credentials API** from the *Select Experience* list. 4. Select **Request credentials**. 5. Select the credential you wish to send to the verifier from the list of available apps suggested by the operating system. 6. Send the response. 7. The application extension will close, and you should see a successful verification indication in the browser where you initiated the request. #### Cross-device workflow [#cross-device-workflow] 1. Run the app and then close it (this updates the app on your testing device). 2. Use a desktop browser to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). 3. Select **Digital Credentials API** from the *Select Experience* list. 4. Select **Request credentials**. 5. Open the camera on your testing iOS device and scan the QR code.\ (When using Safari and an iOS device with the same Apple ID, the request is automatically transferred to the mobile device). 6. Select the credential you wish to send to the verifier from the list of available apps suggested by the operating system. 7. Send the response. 8. The application extension will close. 9. Back on your desktop browser, you should see a successful verification indication. ## Summary [#summary] You have just used the [iOS Holder SDK](/docs/holding/sdk-overview) to build an iOS application that can present a claimed [mDoc](/docs/concepts/mdocs) to a verifier [remotely](/docs/verification/remote-overview) via the [DC API](https://wicg.github.io/digital-credentials), as per Annex C of [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html). This was achieved by making the following adjustments to your application: 1. Create the app extension to handle the DC API presentation requests. 2. Initialize the SDK with support for the DC API enabled. 3. Handle the presentation request in your app extension. ## What's next? [#whats-next] * You can [build a web application](/docs/verification/remote-web-verifiers/dc-api/guide) that will interact with your wallet application via an online presentation workflow using the DC API. * You can build additional capabilities into your new application: * Present a claimed mDoc for verification via a [proximity presentation workflow](/docs/holding/proximity-presentation-tutorial). * You can check out the [iOS Holder SDK reference documentation](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk) for more details on the available functions and classes. # How to build a React Native application that can present a credential via the DC API URL: /docs/holding/dc-api/guide-react-native Digital Credentials (DC) API support is currently offered as a tech preview. The DC API specification itself is still under active development in the W3C Web Incubator CG, and platform implementations continue to evolve. As such, functionality may be limited, may not work in all scenarios, and could change or break without prior notice as browsers and operating systems update their implementations. ## Overview [#overview] This guide demonstrates how to use the [Holder SDK](/docs/holding/sdk-overview) to build a React Native application that can present a claimed [mDoc](/docs/concepts/mdocs) to a verifier [remotely](/docs/verification/remote-overview) via the [DC API](/docs/holding/dc-api/overview). The React Native Holder SDK wraps the native iOS and Android Holder SDKs, so the underlying behaviour matches the [iOS guide](/docs/holding/dc-api/guide-ios) (Annex C of [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html)) and the [Android guide](/docs/holding/dc-api/guide-android) (Annex D). The difference is that you enable and configure the feature from a single JavaScript `initialize` call, while some platform-specific native setup is still required, particularly on iOS. ## Prerequisites [#prerequisites] ### Application base [#application-base] This guide builds on the knowledge gained in the [Claim a credential](/docs/holding/credential-claiming-tutorial) and [Remote presentation](/docs/holding/remote-presentation-tutorial) tutorials. It is recommended to complete those tutorials first, then return here to add support for the [DC API workflow](/docs/holding/dc-api/workflow). ### Testing Devices [#testing-devices] * Mobile device: * An iOS device running iOS 26.0 or later, or an Android device running Android 14 or later. * The device must have a holder application with claimed credentials set up as per the [Claim a credential tutorial](/docs/holding/credential-claiming-tutorial) and support for the DC API enabled as per the instructions in this guide. * Desktop device with a web browser that supports the DC API: * Chrome or a Chromium based browser v138 or later. * Safari v26 or later. ### Development environment [#development-environment] * For iOS, Xcode 26.0.0 or later is required to build the app extension described below. ## How it works [#how-it-works] The DC API is a browser standard that enables web applications to request and verify digital credentials directly from compatible wallet applications on the user's device. This provides a seamless user experience where credential presentation happens entirely within the browser context. From a holder application point of view, there are several changes required to support remote presentations via the DC API compared to the standard [OID4VP remote presentation workflow](/docs/verification/remote-web-verifiers/workflow). The React Native Holder SDK exposes these through a single `dcConfiguration` option on `initialize`, which carries separate configuration for each platform: * On **Android**, enabling the DC API is handled entirely from JavaScript. When you set the Android configuration, the SDK registers all claimed credentials with the [Digital Credentials Manager (DCM)](https://developer.android.com/identity/digital-credentials) and handles incoming presentation requests in the background. * On **iOS**, you configure the DC API from JavaScript (the app group and supported document types), but presentation requests are served by a native [Identity Document Provider app extension](https://developer.apple.com/documentation/technologyoverviews/app-extensions). App extensions do not run in the React Native runtime, so this part must be implemented natively in Swift against the [iOS Holder SDK](/docs/holding/dc-api/guide-ios). Configuration provided for the platform you are not currently running on is ignored, so it is safe to supply both `ios` and `android` configuration in the same call. ## Adjusting your application to support remote presentations via the DC API [#adjusting-your-application-to-support-remote-presentations-via-the-dc-api] You will make the following adjustments to enable the DC API in your application: 1. [Enable the DC API during SDK initialization](#enable-the-dc-api-during-sdk-initialization). 2. [Complete the Android-specific setup](#complete-the-android-specific-setup). 3. [Complete the iOS-specific setup](#complete-the-ios-specific-setup). 4. [Test the application](#test-the-application). ### Enable the DC API during SDK initialization [#enable-the-dc-api-during-sdk-initialization] Enable DC API support by passing a `dcConfiguration` object to `initialize`. The object accepts platform-specific configuration under `ios` and `android` keys: * `android.enabled`: when `true`, all claimed credentials are enrolled with Android's Digital Credentials Manager (DCM) and made available for sharing. * `ios.appGroup`: an [app group](https://developer.apple.com/documentation/xcode/configuring-app-groups) identifier accessible by both your app and its app extension. This must match the app group you configure in the iOS-specific setup below. * `ios.supportedDocTypes`: the document types your holder supports, as declared in your app's entitlements. These are provided via the `SupportedDocType` enum exported by the SDK. ```ts title="Initializing the SDK with DC API support enabled" import MobileCredentialHolder, { SupportedDocType, UserAuthenticationBehavior, } from "@mattrglobal/mobile-credential-holder-react-native"; const result = await MobileCredentialHolder.initialize({ userAuthenticationConfiguration: { userAuthenticationBehavior: UserAuthenticationBehavior.OnDeviceKeyAccess, }, dcConfiguration: { ios: { appGroup: "group.com.yourcompany.yourapp", supportedDocTypes: [ SupportedDocType.mDL, SupportedDocType.photoid, SupportedDocType.eudi, SupportedDocType.euav, SupportedDocType.jpMnc, ], }, android: { enabled: true, }, }, }); if (result.isErr()) { // Handle initialization error console.error(result.error); } ``` When you initialize the SDK with `dcConfiguration`: * All existing claimed credentials are registered for the DC API on the current platform. * Any future credentials issued to the holder are registered automatically. * On Android, the SDK is ready to handle incoming presentation requests in the background. * On iOS, the operating system recognizes your app extension as a valid provider for the configured document types (once the extension is set up as described below). `initialize` must always be called from your React Native (JavaScript) code, which runs in your main application. On iOS, **do not** attempt to initialize the SDK from within the app extension — see the iOS-specific setup below. ### Complete the Android-specific setup [#complete-the-android-specific-setup] On Android, setting `android.enabled` to `true` in `dcConfiguration` is all that is required to enable the DC API. The SDK registers your application as a credential provider, registers all claimed credentials with the Digital Credentials Manager, and handles incoming presentation requests and user authentication in the background — without any further involvement from your application. #### Handle verifier authentication results (optional) [#handle-verifier-authentication-results-optional] As with the native Android SDK, you can optionally detect whether the verifier can be trusted and require user confirmation accordingly. This is handled natively in your Android project (via intent filters in `AndroidManifest.xml` and your Android activity), not from JavaScript. If you need this, follow the *Handle verifier authentication results* step in the [Android guide](/docs/holding/dc-api/guide-android#handle-verifier-authentication-results-optional). The intent filters, actions, and `VerifierAuthenticationResult` handling are identical for a React Native application, because they live in the native Android layer of your app. If you do not implement this, the SDK still handles the presentation flow and user authentication with the operating system in the background. ### Complete the iOS-specific setup [#complete-the-ios-specific-setup] On iOS, presentation requests are served by a native **Identity Document Provider app extension** rather than your main application. Because app extensions do not run the React Native runtime, this extension must be implemented natively in Swift, using the [iOS Holder SDK](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk) directly. The JavaScript `initialize` call you set up above handles registering your credentials with the DC API. The remaining iOS work is native Xcode configuration and the extension itself, which is the same as for a native iOS application. Follow the [iOS guide](/docs/holding/dc-api/guide-ios) for the full walkthrough, and apply the React Native-specific notes below. From the [iOS guide](/docs/holding/dc-api/guide-ios), complete the following in your app's `ios` Xcode workspace: 1. [Configure your Xcode project to support the new app extension](/docs/holding/dc-api/guide-ios#configure-your-xcode-project-to-support-the-new-app-extension) — set the deployment target, enable Keychain Sharing, add an App Group, and add the Digital Credentials provider entitlement. 2. [Create and configure an Identity Document Provider extension](/docs/holding/dc-api/guide-ios#create-and-configure-an-identity-document-provider-extension) — add the extension target and give it the same Keychain Sharing group and App Group as the main app. 3. [Handle the presentation request in the app extension](/docs/holding/dc-api/guide-ios#handle-the-presentation-request-in-the-app-extension) — implement the extension handler that creates the presentation session, shows the consent UI, and sends the response. When applying those steps to a React Native project, note the following differences: * **The App Group must match your JavaScript configuration.** The app group you configure on the main app target and the extension target must exactly match the `dcConfiguration.ios.appGroup` value you passed to `initialize` in your React Native code. A common approach is to define the app group once as a Swift constant for the extension and pass the same string from JavaScript. * **Initialize the SDK from React Native, not from the extension.** Your main application initializes the SDK through the React Native `initialize` call, so you do **not** add the main-app initialization step from the iOS guide. The app extension does still call `initializeAppExtension(appGroup:)` (as shown in the iOS guide) so it can read the shared credential storage. Do not call the SDK's `initialize` function from within the app extension. The SDK must only be initialized from your main application — in a React Native app this is your `initialize` call in JavaScript. The extension uses `initializeAppExtension(appGroup:)` instead. Calling `initialize` from the extension can overwrite storage keys and save them under an incorrect Keychain access group, causing initialization failures that are difficult to recover from. * **Add the native iOS Holder SDK to the extension target.** The extension imports `MobileCredentialHolderSDK` (the native iOS SDK) directly, as described in the iOS guide. This is separate from the `@mattrglobal/mobile-credential-holder-react-native` package your JavaScript code uses, and is added to the extension target via the framework or Swift Package Manager. The presentation UI and handler code inside the extension are pure Swift and identical to the iOS guide — there is no React Native equivalent, because the extension runs outside the React Native runtime. ### Test the application [#test-the-application] Let's test that the application is working as expected in both workflows. On iOS, run the app and then close it (this updates the app and its extension on your testing device) before testing. #### Same-device workflow [#same-device-workflow] 1. Use a browser on your testing mobile device to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). 2. Select **Digital Credentials API** from the *Select Experience* list. 3. Select **Request credentials**. 4. Confirm interacting with the verifier on your mobile device when prompted by the operating system (this might look different based on the device and operating system version). 5. Select the credential you wish to send to the verifier from the list suggested by the operating system. 6. Send the response. 7. The UI prompt will close, and you should see a successful verification indication in the browser where you initiated the request. #### Cross-device workflow [#cross-device-workflow] 1. Use a desktop browser to navigate to the [MATTR Labs remote presentation testing tool](https://tools.mattrlabs.com/verify-credentials). 2. Select **Digital Credentials API** from the *Select Experience* list. 3. Select **Request credentials**. 4. Open the camera on your testing device and scan the QR code.\ (On iOS, when using Safari and a device with the same Apple ID, the request is automatically transferred to the mobile device.) 5. Confirm interacting with the verifier on your mobile device when prompted by the operating system (this might look different based on the device and operating system version). 6. Select the credential you wish to send to the verifier from the list suggested by the operating system. 7. Send the response. 8. The UI prompt will close. 9. Back on your desktop browser, you should see a successful verification indication. ## Summary [#summary] You have just used the [Holder SDK](/docs/holding/sdk-overview) to build a React Native application that can present a claimed [mDoc](/docs/concepts/mdocs) to a verifier [remotely](/docs/verification/remote-overview) via the [DC API](https://wicg.github.io/digital-credentials). This was achieved by making the following adjustments to your application: 1. Enable the DC API during SDK initialization by passing a `dcConfiguration`. 2. On Android, no further setup is required (verifier authentication handling is optional). 3. On iOS, create and configure a native Identity Document Provider app extension to handle presentation requests. ## What's next? [#whats-next] * You can [build a web application](/docs/verification/remote-web-verifiers/dc-api/guide) that will interact with your wallet application via an online presentation workflow using the DC API. * You can build additional capabilities into your new application: * Present a claimed mDoc for verification via a [proximity presentation workflow](/docs/holding/proximity-presentation-tutorial). * You can check out the [React Native Holder SDK reference documentation](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html) for more details on the available functions and classes. # Remote web presentation DC API journey pattern URL: /docs/holding/dc-api/journey-pattern This journey pattern is used to verify an mDoc remotely via an [online verification workflow](/docs/verification/remote-overview), as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) using the [DC API](/docs/verification/remote-web-verifiers/dc-api/overview). ## Overview [#overview] * **Issuance channel**: Remote, unsupervised * **Device/s**: Same-device / Cross-device * **Formats**: mDocs * **Information assurance level**: High * **Identity assurance level**: High ## Journey flow [#journey-flow] mDocs Web Verification Same-device ## Architecture [#architecture] Remote web verification DC API architecture ### Interacting with the verifier application [#interacting-with-the-verifier-application] The user accesses a verifier web application using a supported web browser, on either a desktop or a mobile device. ### Requesting a credential for verification [#requesting-a-credential-for-verification] Within the verifier application, the user initiates an interaction that requires presenting a mobile document (mDoc) for verification. The verifier application embeds the MATTR Verifier Web SDK and first checks whether the user’s browser supports the Digital Credentials API (DC API). If supported, the verifier application uses the SDK to initiate a presentation session with a configured MATTR VII verifier tenant. The request sent to the MATTR VII verifier tenant specifies: * Which credentials are required * Which claims from those credentials are needed for verification The MATTR VII verifier tenant creates a new presentation session and returns a verification request object to the verifier application. ### Invoking the Digital Credentials API [#invoking-the-digital-credentials-api] The verifier application passes the verification request object to the browser to invoke the Digital Credentials API. Based on the user’s device and environment, the browser presents an appropriate verification interface to the user: * On desktop devices, this may be rendered as a QR code * On mobile devices, this may be rendered as an in-browser control (for example, a button) to start the verification directly The user initiates the verification process by scanning the QR code or interacting with the in-browser control. ### Selecting and reviewing a credential [#selecting-and-reviewing-a-credential] Once initiated, the browser forwards the verification request to the user’s mobile device. The mobile operating system displays a system-managed interface listing matching credentials available from installed wallet applications that are registered to handle DC API requests. This interface is rendered by the mobile device and appears on top of the web browser. The user selects a credential to present. * On some platforms (for example, iOS), if only one compatible wallet holds a matching credential, the operating system may directly open that wallet without showing a selection interface. ### Invoking the wallet application [#invoking-the-wallet-application] The wallet application authenticates the user and retrieves the verification request details, including: * Which credential is being requested * Which claims are required * The relying party requesting the information The wallet application displays the credential details to the user for review. This interface is rendered by the wallet application and is displayed on top of the web browser. The user reviews the request and, if comfortable, provides consent to share the credential. ### Verifying the credential [#verifying-the-credential] After consent is given, the wallet application returns an encrypted presentation response to the browser. The browser forwards this presentation response to the verifier application, which then submits it to the MATTR VII verifier tenant. The MATTR VII verifier tenant decrypts and verifies the presentation, performing checks to ensure: * The credential has not been tampered with * The credential has not been revoked or suspended * The credential has not expired * The credential was issued by a trusted issuer ### Displaying verification results [#displaying-verification-results] The MATTR VII verifier tenant returns the verification results to the verifier application. The verifier application displays the results to the user, allowing them to continue their interaction based on the outcome of the verification. The MATTR VII verifier tenant can also be configured to return the verification result to a secure back-end service instead of the front-end, depending on implementation needs. # Digital Credentials API URL: /docs/holding/dc-api/overview Description: Learn what the W3C Digital Credentials API (DC API) means for credential holders and wallet builders. How wallets register credentials with the operating system, how holders experience the unified credential picker, and how MATTR Holder SDKs and MATTR GO Hold support the DC API alongside OEM wallets. DC API support is currently offered as a tech preview. The Digital Credentials API specification itself is still under active development in the W3C Web Incubator CG, and platform implementations continue to evolve. As such, functionality may be limited, may not work in all scenarios, and could change or break without prior notice as browsers and operating systems update their implementations. ## What is the Digital Credentials API for holders? [#what-is-the-digital-credentials-api-for-holders] The **Digital Credentials API (DC API)** is a web browser standard that lets credentials held in any compatible wallet respond to credential requests from websites, directly through the browser. For a holder, this means presenting a credential to a website without having to leave the browser, choose between several wallet apps, or guess which app holds the credential the website is asking for. The DC API is being incubated in the [W3C Web Incubator Community Group](https://wicg.github.io/digital-credentials/) and is incorporated into the [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) technical specification for remote verification of mDocs. This page is written from the **holder** perspective, and is most relevant to teams building wallets and holder experiences. For the verifier perspective, see the [verifier DC API overview](/docs/verification/remote-web-verifiers/dc-api/overview). ## What changes for holders [#what-changes-for-holders] Before the DC API, online credential presentation typically meant a long redirect: the website triggered a wallet via a custom URL scheme, the holder was bounced into a wallet app, and the wallet redirected back to the website with a response. That model created familiar problems for holders: * **The "NASCAR" wallet picker**: holders faced an overwhelming screen of wallet options, similar to the multitude of sponsor logos on NASCAR vehicles. * **Dead-ends**: holders picked a wallet that turned out not to be installed, or that did not contain the credential the website was asking for. * **Lost context**: redirects pulled the holder out of the website they were trying to complete a task on, sometimes losing in-progress state. The DC API shifts the experience from **choosing a wallet** to **choosing the right credential**. The operating system queries every registered wallet on the device, aggregates matching credentials, and presents them to the holder in a single, unified picker. The holder picks the credential they want to share. The wallet that holds it serves the request behind the scenes. For the holder, the practical consequences are: * A single, OS-native picker, regardless of how many wallets are on the device. * Only credentials that match the request appear, removing guesswork. * No need to leave the browser, which keeps the holder's flow intact. * A familiar authentication step (platform biometrics or device passcode) before sharing. ## How wallets participate in the DC API [#how-wallets-participate-in-the-dc-api] For a credential to appear in the operating system's picker, the wallet that holds it must register it with the OS as available for DC API requests. This registration happens when the wallet stores the credential, and is refreshed as credentials are added, updated, or removed. At a high level, a DC API-capable wallet is responsible for: * **Registering** credentials with the operating system so they can be discovered by DC API requests. * **Matching** registered credentials against incoming DC API requests so the OS can surface only the relevant ones to the holder. * **Authenticating the holder** at presentation time, typically using platform-level biometrics or a device passcode. * **Creating and signing the verifiable presentation** using the cryptographic key bound to the credential, which remains secure in the device's key store. * **Returning the presentation** through the DC API so the browser can deliver it back to the verifier's session. From the holder's point of view none of this is visible. The wallet that actually serves the request is just the one that holds the credential the holder selected. ## How a holder experiences a DC API request [#how-a-holder-experiences-a-dc-api-request] A DC API-based verification flow looks roughly like this from the holder's perspective. ### The website asks for a credential [#the-website-asks-for-a-credential] While interacting with a website, the holder reaches a point where the website needs to verify something - identity, age, entitlement. The website triggers a DC API request in the browser. The holder does not see a wallet selection screen. They stay in the website's context. ### The operating system shows matching credentials [#the-operating-system-shows-matching-credentials] The operating system looks across every wallet that has registered credentials and finds the ones that match the request. It then shows the holder a unified picker that includes only those matching credentials. If only one credential matches, some platforms may skip directly to the next step. ### The holder authenticates and consents [#the-holder-authenticates-and-consents] The holder selects the credential they want to share. The operating system asks the holder to authenticate using platform biometrics or a device passcode, and shows what information will be shared with the verifier. If the holder cancels, the website is returned to its original state without losing context. ### The wallet creates and shares the presentation [#the-wallet-creates-and-shares-the-presentation] Behind the scenes, the wallet that holds the selected credential creates and signs a verifiable presentation using the credential's private key, which never leaves the device. The presentation is returned through the DC API to the website. The holder remains in the browser throughout. There is no app switch and no return redirect. ## Same-device and cross-device flows for holders [#same-device-and-cross-device-flows-for-holders] The DC API supports two interaction patterns: * **Same-device flows**: The holder is using the website on the same mobile device that holds their credentials. The DC API surfaces matching credentials directly, and the exchange happens entirely within the browser on that device. * **Cross-device flows**: The holder is using the website on a different device, typically a desktop or laptop. The desktop browser displays a QR code (or similar mechanism). The holder scans it with their mobile device, which then invokes the DC API on the mobile platform. Matching credentials are presented on the mobile device. The holder authenticates and consents on the mobile device, and the presentation is delivered back to the desktop browser to continue the task. Cross-device flows use operating-system-level proximity checks (such as CTAP 2.1 over Bluetooth Low Energy) to confirm the two devices are physically close together before the exchange proceeds. This protects against attacks where someone tries to use a screenshot of a QR code or hijack a session from afar. ## Security and privacy properties for holders [#security-and-privacy-properties-for-holders] The DC API is designed with several properties that directly benefit holders: * **No silent tracking**: The DC API prevents unauthorized apps from silently tracking credential interactions. Sharing requires explicit holder action. * **Platform-level authentication**: The holder authenticates using the same biometric or passcode mechanism they already use to unlock the device. The wallet does not need to re-implement authentication primitives, which reduces the chance of weaker custom implementations. * **Keys stay on the device**: The private keys associated with credentials remain in the device's secure key store. Presentations are signed locally rather than by handing keys to a remote service. * **Selective disclosure**: When the underlying credential format supports it (such as mDocs), the holder can share only the specific claims the website asks for, rather than the whole credential. * **Session continuity**: If the holder cancels, the website returns to its original state. The holder is not left in an ambiguous redirect state. * **Proximity protection in cross-device flows**: The browser and operating system enforce proximity checks for cross-device flows, which reduces the risk of remote QR-code based attacks. ## Platform support [#platform-support] The DC API is a web browser standard and is implemented differently across platforms: * **iOS**: Supported on Safari version 26 and later, available on iPhone 11 and later running iOS 26. * **Android**: Supported on Chrome and Chromium-based browsers version 138 and later. The DC API is specifically designed for web applications running in browsers. For native holder apps, the platform-specific APIs are used instead, for example Android's DigitalCredential API, which is part of the Credential Manager framework. ISO/IEC 18013-7 defines two browser-level mechanisms that are relevant here: * **Annex C**: mDoc device retrieval over the DC API, currently supported on iOS and Safari. * **Annex D** (draft): OpenID4VP over the DC API, expected on Android and Chromium-based browsers. A wallet that supports both annexes provides a consistent holder experience across platforms and browser types. ## How MATTR supports the DC API on the holder side [#how-mattr-supports-the-dc-api-on-the-holder-side] MATTR provides two main paths for building holder experiences that work with the DC API: * **[MATTR Pi Holder SDKs](/docs/holding/sdk-overview)**: Build your own wallet app, or add credential holding capabilities to an existing app. React Native, iOS, and Android SDKs are available, and integrate with the DC API where the platform supports it. The SDKs handle credential registration with the operating system, authentication, and presentation signing. * **[MATTR GO Hold](/docs/holding/go-hold/getting-started)**: A white-label wallet application that already includes DC API support. Apply your own branding without writing wallet code yourself. In both cases, credentials stored in the wallet are registered with the operating system so they are discoverable through DC API requests, alongside other wallets on the device including OEM wallets. The holder sees a single picker and chooses the credential they want to share. For platform-specific holder integration steps, see the [iOS guide](/docs/holding/dc-api/guide-ios), [Android guide](/docs/holding/dc-api/guide-android), and [React Native guide](/docs/holding/dc-api/guide-react-native). ## How OEM wallets fit into the holder ecosystem [#how-oem-wallets-fit-into-the-holder-ecosystem] A growing share of high-assurance identity credentials, such as mobile driver's licenses and national identification cards, are issued into **OEM wallets** - the credential wallets shipped natively on iOS and Android by Apple and Google. From a holder's perspective, the DC API treats OEM wallets the same as any other compatible wallet on the device. They register their credentials with the operating system, and those credentials appear in the same unified picker. The practical consequence for holders is that a credential issued into an OEM wallet can be presented to any website that supports the DC API, side by side with credentials held in third-party or MATTR-built wallets. Holders do not have to install additional apps to use OEM-held credentials, and do not have to remember which wallet a particular credential lives in. For wallet builders and ecosystem partners thinking about how OEM wallets fit alongside their own holder experience, [contact us](mailto:dev-support@mattr.global) to talk through the design. ## Frequently asked questions [#frequently-asked-questions] ### What is the Digital Credentials API (DC API) from a holder's perspective? [#what-is-the-digital-credentials-api-dc-api-from-a-holders-perspective] The **Digital Credentials API (DC API)** is a browser standard that lets credentials in any wallet on a device respond to requests from websites without redirecting the holder out of the browser. For holders, the practical effect is a single, operating-system-level prompt that shows credentials matching what the website is asking for, regardless of which wallet they live in. ### How does a wallet participate in the DC API? [#how-does-a-wallet-participate-in-the-dc-api] A wallet that supports the DC API **registers the credentials it holds with the operating system**. When a website triggers a DC API request, the operating system queries every registered wallet, aggregates the matching credentials, and presents them to the holder in a unified picker. When the holder selects a credential, the originating wallet creates and signs the presentation. ### How is the DC API different from redirect-based wallet flows for holders? [#how-is-the-dc-api-different-from-redirect-based-wallet-flows-for-holders] In a redirect-based flow, the holder is bounced out of the website into a wallet app and then back, often after first having to pick a wallet from a long list. With the DC API, the holder **stays in the browser**. The operating system surfaces only the credentials that match the request, and the holder picks the **credential**, not the wallet. This tends to be faster, less confusing, and less prone to dead-ends. ### Do MATTR-built wallets support the DC API? [#do-mattr-built-wallets-support-the-dc-api] Yes. MATTR-built wallets, including holder applications built with the **MATTR Pi Holder SDKs** and **MATTR GO Hold**, register the credentials they store with the operating system so they are discoverable through DC API requests. This lets MATTR-built wallets sit alongside other wallets, including OEM wallets, in the operating system's unified credential picker. ### How do OEM wallets fit into the DC API ecosystem? [#how-do-oem-wallets-fit-into-the-dc-api-ecosystem] **OEM wallets** are credential wallets shipped natively by device manufacturers such as Apple and Google. Because OEM wallets register credentials with the operating system in the same way other DC API-compatible wallets do, holders can present credentials from OEM wallets and third-party wallets through the same browser experience. This expands the set of credentials a holder can use for online verification without having to install additional apps. ### Which browsers and platforms currently support the DC API for holders? [#which-browsers-and-platforms-currently-support-the-dc-api-for-holders] The DC API is supported on **iOS 26 and later with Safari 26** (on iPhone 11 and later), and on **Chrome and Chromium-based browsers version 138 and later** on Android. Holders need a wallet that has registered credentials with the operating system, and a browser and OS version that support the DC API. # Remote web presentation DC API workflow URL: /docs/holding/dc-api/workflow DC API support is currently offered as a tech preview. The Digital Credentials API specification itself is still under active development in the W3C Web Incubator CG, and platform implementations continue to evolve. As such, functionality may be limited, may not work in all scenarios, and could change or break without prior notice as browsers and operating systems update their implementations. mDocs are digital credentials based on the ISO/IEC [18013-5](https://www.iso.org/standard/69084.html) standard and [18013-7](https://www.iso.org/standard/91154.html) technical specification, designed to be stored on a holder’s mobile device and support either in-person or remote verification workflows. The purpose of this page is to describe the end-to-end [remote web app verification](/docs/verification/remote-overview) DC API workflow, where a user interacts with a web application to present an mDoc stored on their mobile device via the DC API, as per Annex C and D of ISO/IEC 18013-7:2025. ## Prerequisites [#prerequisites] We recommend you make yourself familiar with the following concepts to support your understanding of the implementation described in this page: * What is [credential verification](/docs/verification)? * What are [mDocs](/docs/concepts/mdocs)? * What is [remote verification](/docs/verification/remote-overview)? * What is the [DC API](/docs/verification/remote-web-verifiers/dc-api/overview)? ## Overview [#overview] The DC API is a browser standard that enables web applications to request and verify digital credentials directly from compatible wallet applications on the user's device. This provides a seamless user experience within a platform-managed flow initiated from the browser. The API allows wallets to register as credential providers with the mobile device's operating system, making them automatically discoverable when a website requests credentials. ## Detailed workflow [#detailed-workflow] Let's take a closer look at the end-to-end workflow and explain the role of each component: ### Invoking the interaction [#invoking-the-interaction] The user triggers an action in a verifier web application that requires presenting a credential for verification. ### Generating challenge [#generating-challenge] When the verifier web application has a backend, the backend generates a unique challenge to ensure the security of the verification workflow. This challenge will be used to validate that the verification results received later are associated with this specific session. Generating the challenge on the backend helps prevent tampering and replay attacks, as the challenge is not exposed to the frontend until verification is complete. Furthermore, it enables the backend to associate the verification results with an existing session or transaction, enhancing the overall security and integrity of the verification process. ### Passing challenge to web application [#passing-challenge-to-web-application] The verifier backend sends the generated challenge to the verifier web application, which will include it when starting the presentation session with MATTR VII. ### Checking browser support [#checking-browser-support] The verifier web application checks if the user's browser supports the DC API. ### Requesting credentials [#requesting-credentials] The **verifier web application** calls the SDK's `requestCredentials` method to start a presentation session with the configured **MATTR VII tenant**, including the challenge received from the backend. ### Creating presentation session [#creating-presentation-session] The **MATTR VII tenant** creates a new presentation session and returns the request object to the **verifier web application**. ### Invoking DC API [#invoking-dc-api] The **verifier web application** passes the request object to the user's **browser** to invoke the Digital Credentials API. ### Presenting verification request [#presenting-verification-request] The **browser** presents a verification request interface to the **user**. This can be a QR code (when the user is on a desktop) or a button to start the process directly in the browser (when the user is on a mobile device). ### Initiating verification [#initiating-verification] The **user** initiates the verification process by scanning the QR code/clicking the button. ### Forwarding request to mobile device [#forwarding-request-to-mobile-device] The **browser** forwards the request to the **mobile device**. ### Displaying matching credentials [#displaying-matching-credentials] The **mobile device** displays to the **user** matching credentials from installed **wallet applications** that are registered to handle DC API requests. This UI is rendered by the **mobile device** and is displayed on top of the web browser. ### Selecting credential [#selecting-credential] The **user** selects a credential from the displayed options. ### Forwarding to wallet [#forwarding-to-wallet] The **mobile device** forwards the verification request to the **wallet application** that holds the selected credential. On iOS devices, if there is only one compatible wallet that holds a matching credential, the system may directly open that wallet without displaying the selection UI to the user. ### Reviewing credential details [#reviewing-credential-details] The mobile device displays the credential details to the **user** for review and consent. This UI is rendered by the **wallet application** and is displayed on top of the web browser in iOS, while on Android it is displayed within the system's native UI. ### Providing consent [#providing-consent] The **user** reviews the credential details and provides consent to share them with the verifier. ### Returning presentation response [#returning-presentation-response] The **wallet application** returns the encrypted credential as a presentation response to the **browser**. ### Forwarding to Verifier [#forwarding-to-verifier] The **browser** forwards the presentation response to the **verifier web application**. ### Submitting for verification [#submitting-for-verification] The **verifier web application** submits the presentation response to the **MATTR VII tenant** for verification. ### Verifying credential [#verifying-credential] The **MATTR VII tenant** decrypts and verifies the credential. ### Notifying verification completion [#notifying-verification-completion] The **MATTR VII tenant** notifies the **verifier web application** that verification has been completed. When the web application has a backend, the tenant does not send the verification results directly to the frontend for enhanced security. ### Passing session ID to backend [#passing-session-id-to-backend] The **verifier web application** sends the presentation session ID to the **verifier backend** to retrieve the verification results securely. ### Retrieving verification results [#retrieving-verification-results] The **verifier backend** makes a [request](/docs/verification/remote-verification-api-reference/presentation-sessions#retrieve-a-presentation-session-result) to **MATTR VII** to retrieve the verification results for the session using the session ID. ### Returning results with challenge [#returning-results-with-challenge] The **MATTR VII tenant** responds with the verification results and the unique challenge that was included when the presentation session was created. ### Validating challenge [#validating-challenge] The **verifier backend** compares the original challenge it generated with the challenge received from MATTR VII to ensure the response can be trusted and is associated with the correct session. ### Passing results to web application [#passing-results-to-web-application] Once the challenge is validated, the **verifier backend** sends the verification results to the **verifier web application**. ### Displaying results [#displaying-results] The **verifier web application** displays the verification results and the **user** continues the interaction accordingly. Based on the unique challenge, the verifier can also continue any backend processes associated with the verification session, such as granting access to a service or completing a transaction. # NFC device engagement for proximity presentations URL: /docs/holding/proximity-presentation-guides/nfc-engagement-guide ## Overview [#overview] This guide shows how to configure the Holder SDK to start a proximity presentation session using device engagement over NFC. NFC-based device engagement is a convenient alternative to QR-code engagement: it is faster, simpler for users, and fully handled by the SDK. It also simplifies your application because you do not need to generate a QR code or process engagement data manually. ## Prerequisites [#prerequisites] This guide builds on knowledge obtained in the [Proximity Presentation tutorial](/docs/holding/proximity-presentation-tutorial). It is recommended to complete that tutorial first, then return here to learn how to configure NFC device engagement. ## Understanding NFC Device Engagement [#understanding-nfc-device-engagement] **Device engagement** is the initial stage of a [proximity presentation session](/docs/verification/in-person-overview#engagement-phase) defined in ISO/IEC 18013-5:2021. During engagement, the Holder sends the information required to establish a secure connection to the Verifier. This information contains cipher suite, ephemeral device key, and channel parameters, as well as its preferred BLE role and the associated connection details. In the [Proximity Presentation tutorial](/docs/holding/proximity-presentation-tutorial) this data was encoded into a QR code that the Verifier scanned to establish the channel and encryption. With NFC the Verifier reads the engagement data directly from the Holder device, with no QR code generation, scanning or handling required. NFC-based engagement supports two modes (as per ISO/IEC 18013-5:2021): * *Static Handover*: The Holder sends its connection details to the Verifier. * *Negotiated Handover*: The Verifier sends its supported data transfer options, then the Holder selects one and returns its connection details. A key advantage of NFC-based engagement is that the user can start it without first opening the app. Similar to NFC payments, the user brings the device near the Verifier's device and the Holder app launches automatically. If multiple NFC-capable Holder apps are installed, the system prompts the user to choose one. A default app can be set in the device's system settings. When multiple NFC-capable wallet apps are installed on a device, tapping the device against an NFC verifier may trigger multiple system prompts asking the user to select which app to use for the NFC interaction. While these selection prompts are expected as part of the Android system behavior, they currently appear more than once during a single engagement, which may cause confusion for end users. The prompt will disappear if the user presses **Back**. ## Configuring NFC-based Engagement [#configuring-nfc-based-engagement] Configuration involves the following steps: 1. Register the NFC-based engagement listener: set up a listener to receive NFC engagement events. 2. Handle device engagement and create a proximity presentation session: respond to engagement events by creating a presentation session. ### Step 1: Register the NFC-based engagement listener [#step-1-register-the-nfc-based-engagement-listener] After you include the SDK, your Holder app becomes NFC-capable and will be launched (or suggested) when near an NFC-enabled verifier. The SDK automatically performs the engagement, but you must register a listener to receive events. Do this as early as possible in the app lifecycle. NFC device engagement is only supported on Android devices. On iOS, the SDK returns a `PlatformNotSupported` error. If you are building a cross-platform React Native app, ensure NFC engagement code only runs on Android builds by using a `Platform.OS` check. Coming soon... Register the listener in `onCreate`: ```kotlin title="HolderApplication.kt" override fun onCreate() { super.onCreate() MobileCredentialHolder.Nfc.setDeviceEngagementListener { error -> if (error == null) { // Create the session and show consent UI (e.g. start an Activity) } else { Log.e("HolderApplication", "NFC engagement error: ${error.message}") } } } ``` Register the listener as early as possible, such as in your app's root component or entry point. Use a `Platform.OS` check to ensure this only runs on Android: ```ts title="App.tsx" import { Platform } from "react-native"; import { setDeviceEngagementListener } from "@mattrglobal/mobile-credential-holder-react-native"; const setupNfcEngagement = async () => { if (Platform.OS !== "android") { return; } const result = await setDeviceEngagementListener((error) => { if (error) { handleError(error); return; } // Engagement successful, create the session and show consent UI }); if (result.isErr()) { console.error("Failed to set NFC listener:", result.error); } }; ``` ### Step 2: Handle device engagement [#step-2-handle-device-engagement] Start a proximity presentation session (the same way as you do [for QR-based device engagement](/docs/holding/proximity-presentation-tutorial#step-1-create-a-qr-code-for-the-verifier-to-scan)) while setting `engagementFromNfc = true` to indicate NFC was used: Coming soon... ```kotlin val mdocHolder = MobileCredentialHolder.getInstance() // Minimal initialization logic // However, if you support multiple instances, you probably want to add more robust initialization logic if (!mdocHolder.initialized) mdocHolder.initialize(activity) mdocHolder.createProximityPresentationSession( activity, onRequestReceived = { _, requests, e -> // Show consent UI for requests or handle error }, engagementFromNfc = true ) ``` ```ts import { createProximityPresentationSession, isInitialized, initialize, } from "@mattrglobal/mobile-credential-holder-react-native"; // Ensure the SDK is initialized const initialized = await isInitialized(); if (!initialized) { await initialize(); } const result = await createProximityPresentationSession({ onRequestReceived: (requests, error) => { if (error) { // Handle error return; } // Show consent UI for the received requests }, engagementFromNfc: true, }); if (result.isErr()) { console.error("Failed to create session:", result.error); } ``` * Your consent UI might appear when the app is already running or as the first screen after launch. * Ensure the SDK is initialized before creating a presentation session. ## Optional steps [#optional-steps] ### Configure branding [#configure-branding] Help users recognize your app in system prompts and settings by overriding NFC engagement resources: Coming soon... 1. Set a description string in `res/values/strings.xml`: ```xml title="res/values/strings.xml" Your application name. ``` This string will appear in the system prompt and in device settings for non-payment NFC services. 2. Add a drawable resource named `mattr_nfc_engagement_icon` containing your app icon. The icon will be displayed by the system along with the description. Branding configuration is managed through native Android resources. In your React Native project's `android` directory: 1. Set a description string in `android/app/src/main/res/values/strings.xml`: ```xml title="android/app/src/main/res/values/strings.xml" Your application name. ``` This string will appear in the system prompt and in device settings for non-payment NFC services. 2. Add a drawable resource named `mattr_nfc_engagement_icon` containing your app icon to `android/app/src/main/res/drawable/`. The icon will be displayed by the system along with the description. ### Set preferred BLE role and log level [#set-preferred-ble-role-and-log-level] Set optional NFC configuration options that apply to subsequent NFC engagements, regardless of SDK initialization. Calling this API does not require the SDK to be initialized. Coming soon... ```kotlin MobileCredentialHolder.Nfc.setNfcConfiguration( context, NfcConfiguration( handoverMode = NfcHandoverMode.Negotiated, bleMode = BleMode.MDocClientCentral, // Used only for Static Handover to set preferred BLE role logLevel = LogLevel.ERROR // Log level if SDK not initialized yet ) ) ``` ```ts import { Platform } from "react-native"; import { setNfcConfiguration, BleMode, NfcHandoverMode, LogLevel, } from "@mattrglobal/mobile-credential-holder-react-native"; if (Platform.OS === "android") { const result = await setNfcConfiguration({ handoverMode: NfcHandoverMode.Negotiated, bleMode: BleMode.MDocClientCentral, // Used only for Static Handover to set preferred BLE role logLevel: LogLevel.Error, // Log level if SDK not initialized yet }); if (result.isErr()) { console.error("Failed to set NFC configuration:", result.error); } } ``` ## Testing NFC-based Engagement [#testing-nfc-based-engagement] To test NFC-based engagement: 1. Claim a credential in a holder app. 2. Use an NFC-capable verifier device and request to verify the credential claimed in the previous step. 3. Bring the devices close to each other to establish NFC connection. 4. Confirm the Holder app launches (or is suggested if multiple NFC-capable apps exist).\ If multiple NFC-capable apps exist, select yours. 5. Approve the verification request. 6. Confirm the credential is presented successfully to the verifier. # Handling verifier authentication URL: /docs/holding/proximity-presentation-guides/verifier-authentication Description: Inspect the verifier authentication result during a proximity presentation so users can decide whether to share credentials with an unauthenticated verifier ## Overview [#overview] When a verifier requests credentials during a proximity presentation, your holder application can inspect whether the verifier proved its identity before any credential data is shared. This lets you show the user who is asking for their data and how trustworthy the request is, and lets the user decline to share with a verifier that cannot be authenticated. In a proximity presentation, the verifier can sign its request. The Holder SDK validates that signature against the trusted verifier root certificates configured on the device and exposes the outcome as a verifier authentication result on each request. Your application reads this result and decides how to proceed. This is the mechanism that ISO/IEC 18013-5:2021 calls *mDoc Reader Authentication* (section 9.1.4). The verifier (the "mdoc reader") signs the request, and the holder ("mdoc") authenticates the reader before responding. The SDK surfaces it under the more general name *verifier authentication*, so the same concept is consistent with the remote presentation flow. This guide covers proximity (ISO/IEC 18013-5) verifier authentication, where the verifier signs the device request and the holder validates it offline. Remote (OpenID4VP / ISO/IEC 18013-7) presentations authenticate the verifier through the signed authorization request instead. See [Handling verifier authentication for remote presentations](/docs/holding/remote-presentation-guides/verifier-authentication) for that flow. ## Prerequisites [#prerequisites] This guide builds on the [Proximity Presentation tutorial](/docs/holding/proximity-presentation-tutorial). Complete that tutorial first so you have a working holder application that can create a proximity presentation session and receive requests, then return here to add verifier authentication handling. Trust evaluation is performed against the trusted verifier root certificates configured on the holder device. Whether a request is reported as trusted depends on those certificates being present. For background on verifier certificates, see [Certificates overview](/docs/verification/certificates/overview). ## Understanding the verifier authentication result [#understanding-the-verifier-authentication-result] When the SDK receives a device request, it evaluates any reader authentication it contains and attaches a verifier authentication result to each requested document. The result is one of three outcomes: * **Trusted**: The request was signed and the signature chained to a trusted verifier root certificate. The result carries a `VerifierInfo` value describing the trusted root that was matched (its common name and the identifier of the stored certificate). * **Untrusted**: A reader signature was present but could not be trusted. The result carries the X.509 certificate chain from the request (as PEM strings) and an error describing the first validation failure detected. * **Unsigned**: The request carried no reader authentication. The result optionally carries the request origin if one is available. An untrusted result reports one of the following reasons. These map directly to the requirements in ISO/IEC 18013-5:2021 section 9.1.4: | Reason | Meaning | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Certificate not found | The verifier authentication object is missing a verifier certificate (section 9.1.4.4). | | Invalid verifier certificate | The verifier certificate in the authentication object is invalid. | | Inactive certificate | The certificate's `validFrom` date is in the future. | | Expired certificate | The certificate's `validUntil` date is in the past. | | Failed to evaluate trust | The certificate chain or the integrity of the verifier authentication object could not be verified, for example because it does not chain to a trusted root. | ## Adding trusted verifier certificates [#adding-trusted-verifier-certificates] A request is reported as trusted only when the verifier's signature chains to a trusted verifier root certificate stored on the device. Your application provisions these certificates through the SDK. The same trusted verifier certificate store is used for both proximity and [remote](/docs/holding/remote-presentation-guides/verifier-authentication) presentations. Certificates are supplied as PEM-encoded or Base64-encoded DER strings and conform to the IACA profile defined in ISO/IEC 18013-5. Adding a certificate is idempotent: adding the same certificate again returns the identifier of the existing entry rather than creating a duplicate. Use [`addTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/addtrustedverifiercertificates\(certificates:\)), [`getTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/gettrustedverifiercertificates\(\)), and [`deleteTrustedVerifierCertificate`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/deletetrustedverifiercertificate\(certificateid:\)): ```swift title="CertificateSettings.swift" let mobileCredentialHolder = MobileCredentialHolder.shared // Add one or more trusted verifier certificates (PEM or Base64 DER). let addedIds = try mobileCredentialHolder.addTrustedVerifierCertificates( certificates: [verifierCertificatePem] ) // List the stored trusted verifier certificates. let stored: [TrustedCertificate] = try mobileCredentialHolder.getTrustedVerifierCertificates() // Remove one by its identifier. try mobileCredentialHolder.deleteTrustedVerifierCertificate(certificateId: addedIds[0]) ``` Each stored certificate is a [`TrustedCertificate`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/trustedcertificate) carrying its `id`, `pem`, and `commonName`. Use [`addTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/add-trusted-verifier-certificates.html), [`getTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/get-trusted-verifier-certificates.html), and [`deleteTrustedVerifierCertificate`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/delete-trusted-verifier-certificate.html): ```kotlin title="CertificateSettings.kt" val mobileCredentialHolder = MobileCredentialHolder.getInstance() // Add one or more trusted verifier certificates (PEM or Base64 DER). val addedIds: List = mobileCredentialHolder.addTrustedVerifierCertificates( listOf(verifierCertificatePem) ) // List the stored trusted verifier certificates. val stored: List = mobileCredentialHolder.getTrustedVerifierCertificates() // Remove one by its identifier. mobileCredentialHolder.deleteTrustedVerifierCertificate(addedIds.first()) ``` Each stored certificate is a `TrustedCertificate` carrying its `id`, `pem`, and `commonName`. Use [`addTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/addTrustedVerifierCertificates.html), [`getTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/getTrustedVerifierCertificates.html), and [`deleteTrustedVerifierCertificate`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/deleteTrustedVerifierCertificate.html): ```ts title="trustedVerifierCertificates.ts" import { addTrustedVerifierCertificates, getTrustedVerifierCertificates, deleteTrustedVerifierCertificate, } from "@mattrglobal/mobile-credential-holder-react-native"; // Add one or more trusted verifier certificates (PEM or Base64 DER). const result = await addTrustedVerifierCertificates([verifierCertificatePem]); if (result.isErr()) { console.error("Failed to add trusted verifier certificates:", result.error); return; } const addedIds = result.value; // List the stored trusted verifier certificates. const stored = await getTrustedVerifierCertificates(); // Remove one by its identifier. await deleteTrustedVerifierCertificate(addedIds[0]); ``` Each stored certificate is a [`TrustedVerifierCertificate`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/TrustedVerifierCertificate.html) carrying its `id`, `pem`, and `commonName`. ## Inspecting the result [#inspecting-the-result] Each requested document delivered to your request handler carries its verifier authentication result. Read it before presenting the consent UI so you can reflect the verifier's status to the user. The session is created exactly as in the [Proximity Presentation tutorial](/docs/holding/proximity-presentation-tutorial#step-2-handle-the-presentation-request); the examples below focus on inspecting the result inside the request handler. On iOS, each entry delivered to your `onRequestReceived` handler is a `MobileCredentialRequest`. Read its [`verifierAuthenticationResult`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialrequest) property and switch over the [`VerifierAuthenticationResult`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/verifierauthenticationresult) cases: ```swift title="ViewModel.swift" func onRequestReceived( _ mobileCredentialRequests: [(request: MobileCredentialRequest, matchedMobileCredentials: [MobileCredentialMetadata])]?, error: Error? ) { guard let mobileCredentialRequests else { // Handle error return } for (request, _) in mobileCredentialRequests { switch request.verifierAuthenticationResult { case .trusted(let verifierInfo): // The request was signed by a trusted verifier root certificate. print("Trusted verifier: \(verifierInfo.commonName)") case .untrusted(let x509CertChain, let error): // A reader signature was present but could not be trusted. print("Untrusted verifier: \(error.localizedDescription)") print("Presented chain: \(x509CertChain)") case .unsigned(let origin): // No reader authentication was provided. print("Unsigned request from: \(origin ?? "unknown origin")") } } } ``` See [`VerifierInfo`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/verifierinfo) and [`VerifierAuthenticationError`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/verifierauthenticationerror) for the associated values. On Android, each item delivered to your `onRequestReceived` callback is a `CredentialRequestInfo` whose `request` property is a `MobileCredentialRequest`. Read its `verifierAuthenticationResult` and match over the [`VerifierAuthenticationResult`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.issuance.dto/-verifier-authentication-result/index.html) subtypes: ```kotlin title="ProximityPresentationViewModel.kt" session = MobileCredentialHolder.getInstance().createProximityPresentationSession( activity, onRequestReceived = { _, requests, error -> if (error != null) { // Handle error return@createProximityPresentationSession } requests?.forEach { requestInfo -> when (val result = requestInfo.request.verifierAuthenticationResult) { is VerifierAuthenticationResult.Trusted -> // The request was signed by a trusted verifier root certificate. Log.i(TAG, "Trusted verifier: ${result.verifierInfo.commonName}") is VerifierAuthenticationResult.Untrusted -> // A reader signature was present but could not be trusted. Log.w(TAG, "Untrusted verifier: ${result.error.message}") is VerifierAuthenticationResult.Unsigned -> // No reader authentication was provided. Log.i(TAG, "Unsigned request from: ${result.origin ?: "unknown origin"}") } } } ) ``` See [`VerifierInfo`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.issuance.dto/-verifier-info/index.html) and [`VerifierAuthenticationFailureType`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.issuance.dto/-verifier-authentication-failure-type/index.html) for the associated values. On React Native, the request handler receives a single `data` argument. When the request succeeds, `data.request` is an array of entries whose `request` property is a `MobileCredentialRequest`. Read its `verifierAuthenticationResult` and switch on the `result` discriminator of [`VerifierAuthenticationResult`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/VerifierAuthenticationResult.html): ```ts title="usePresentations.ts" const result = await createProximityPresentationSession({ onRequestReceived: (data) => { if ("error" in data) { // Handle error return; } data.request.forEach(({ request }) => { const auth = request.verifierAuthenticationResult; switch (auth.result) { case "trusted": // The request was signed by a trusted verifier root certificate. console.log(`Trusted verifier: ${auth.verifierInfo.commonName}`); break; case "untrusted": // A reader signature was present but could not be trusted. console.warn(`Untrusted verifier: ${auth.error.message}`); break; case "unsigned": // No reader authentication was provided. console.log(`Unsigned request from: ${auth.origin ?? "unknown origin"}`); break; } }); }, }); if (result.isErr()) { console.error("Failed to create session:", result.error); } ``` See [`VerifierInfo`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/VerifierInfo.html) and [`VerifierAuthenticationError`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/VerifierAuthenticationError.html) for the associated values. ## What the outcomes mean [#what-the-outcomes-mean] The SDK reports the verifier authentication result but does not act on it. What each outcome tells your application: * **Trusted**: The request was signed and the signature chained to a trusted verifier root certificate. The `VerifierInfo` identifies which root was matched, including its common name. * **Untrusted**: A reader signature was present but could not be validated. The result includes the reason and the certificate chain that was presented. * **Unsigned**: The request carried no reader authentication, so the verifier's identity was not established by the request. How an application responds to each outcome is a product and trust-framework decision that the SDK does not make for you. Different implementations make different choices depending on their ecosystem and risk tolerance. For example, an application might: * refuse to respond to unsigned or untrusted requests; * accept any request and record the outcome for audit; * present the outcome to the user and let them decide whether to continue; * proceed automatically for trusted requests while requiring explicit user confirmation for unsigned or untrusted ones. Whichever approach you take, the outcome is available before you send a response, so you can carry it into your flow and respond only for the credentials involved, as shown in the [Proximity Presentation tutorial](/docs/holding/proximity-presentation-tutorial#step-3-send-a-response). Many legitimate verifiers do not sign their requests, and validation can fail for benign reasons such as a recently rotated certificate that is not yet trusted on the device. The result describes what could be established about the verifier; what that means for a given interaction depends on your ecosystem. ## Testing verifier authentication [#testing-verifier-authentication] To exercise each outcome: 1. Claim a credential in your holder application. 2. Present to a verifier that signs its request with a reader certificate that chains to a root trusted on the device, and confirm the result is reported as trusted with the expected common name. 3. Present to a verifier whose reader certificate is not trusted on the device (for example, expired or signed by an unknown root), and confirm the result is reported as untrusted with the expected reason. 4. Present to a verifier that does not sign its request, and confirm the result is reported as unsigned. # URI scheme handling for verification requests URL: /docs/holding/remote-presentation-guides/handling-uri-schemes Description: Learn how to configure your holder application to handle different types of authorization request URI schemes for credential presentations ## Overview [#overview] When verifiers create OID4VP verification requests and share them with holders, they can use different URI schemes to control which wallet applications can handle those requests and how the user experience flows. Your holder application needs to be properly configured to handle the URI schemes that verifiers in your ecosystem are using. This guide explains the different URI scheme types and shows you how to configure your holder application to handle each type of URI scheme. ## Understanding URI schemes in verification requests [#understanding-uri-schemes-in-verification-requests] A URI scheme is the protocol part at the beginning of a URI (such as `https://`, `mailto:`, or custom schemes like `mdoc-openid4vp://`). When a verifier generates an OID4VP verification request, they choose which URI scheme to use based on their requirements for security, user experience, and control over which apps can respond to the request. Several URI scheme types can be used for OID4VP verification requests: 1. **ISO 18013-7 default scheme** (`mdoc-openid4vp://`): Defined by ISO/IEC 18013-7:2025. A wallet that handles this scheme is declaring compliance with the full ISO 18013-7 configuration — it signals protocol support, not just routing. This includes, among other requirements, support for ECDH-ES as `authorization_encryption_alg_values_supported`. Any app registered to handle this scheme can respond to the authorization request. 2. **Private-use URI scheme** (`com.example.wallet://`): A unique custom scheme using reverse-domain notation. This makes it less likely (but not impossible) for other apps to handle the request. Unlike the default schemes above, a private-use scheme carries no implied protocol configuration — the verifier must either know the wallet's supported capabilities out-of-band, or discover them dynamically (for example, via [OAuth authorization server metadata](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-wallet-metadata-authorizati)). 3. **Claimed HTTPS scheme** (`https://example.com/wallet/...`): Uses domain-verified App Links (Android) or Universal Links (iOS) to ensure only your specific app can handle verification requests from your domain. This is the most secure option, but also requires the most setup and coordination with the verifier to ensure the necessary files and settings are correctly implemented. For a detailed explanation of how these schemes work, their trade-offs, and security considerations, see the [OID4VP workflow documentation](/docs/verification/remote-web-verifiers/workflow#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application). ## Prerequisites [#prerequisites] This guide assumes you have: * Completed the [Remote Presentation Tutorial](/docs/holding/remote-presentation-tutorial) and have a working holder application that can present a credential remotely via OID4VP. All examples in this guide build on top of the tutorial application. * Understanding of how [OID4VP verification requests](/docs/verification/oid4vp) are created and structured. * Access to modify your application configuration and code. For HTTPS schemes (App Links/Universal Links), you will also need: * A domain you control. * Ability to host verification files on your web server. ## Configuring URI scheme handling [#configuring-uri-scheme-handling] The configuration required depends on which URI scheme types you want to support and which platform you're developing for. ### ISO 18013-7 default scheme (`mdoc-openid4vp://`) [#iso-18013-7-default-scheme-mdoc-openid4vp] The `mdoc-openid4vp://` scheme is defined by ISO/IEC 18013-7:2025 and signals that your wallet supports the static wallet metadata parameters defined in ISO 18013-7. Register this scheme if your holder application is targeting ISO 18013-7 compliance. **Configure scheme** 1. Open your project in Xcode. 2. Select your app target and navigate to the **Info** tab. 3. Expand **URL Types** and add a new entry with: * **Identifier**: `mdoc-openid4vp` * **URL Schemes**: `mdoc-openid4vp` **Configure intent filter** 1. Open your `AndroidManifest.xml` file and add the following intent filter within your main activity: ```xml title="AndroidManifest.xml" ``` **Configure scheme** 1. Open your `app.config.ts` file and configure the scheme: ```ts title="app.config.ts" export default ({ config }: ConfigContext): ExpoConfig => ({ // ... other config scheme: "mdoc-openid4vp", // ... other config }); ``` For Expo projects, this configuration automatically sets up the scheme for both iOS and Android. ### Private-use URI scheme [#private-use-uri-scheme] To handle authorization requests using a custom scheme like `com.yourcompany.wallet://`, you need to register that unique scheme with the operating system. **Step 1: Register your custom scheme** 1. Open your project in Xcode. 2. Select your app target and navigate to the **Info** tab. 3. Expand **URL Types** and select the **+** button. 4. Enter: * **Identifier**: `com.yourcompany.wallet` * **URL Schemes**: `com.yourcompany.wallet` **Step 2: Handle incoming URLs** In your `ContentView.swift` file, add a handler for the custom scheme URLs. Add the following extension to your ViewModel: ```swift title="ContentView.swift" // MARK: Handle URL extension ViewModel { func handleIncomingURL(_ url: URL) { guard url.scheme == "com.yourcompany.wallet" else { return } // Extract the authorization request from the URL // The request is typically base64-encoded in the path or as a query parameter if let requestString = extractAuthorizationRequest(from: url) { Task { await createOnlinePresentationSession(authorizationRequestURI: requestString) } navigationPath.append(NavigationState.onlinePresentation) } } func extractAuthorizationRequest(from url: URL) -> String? { // Example: com.yourcompany.wallet://authorize/BASE64_ENCODED_REQUEST if url.host == "authorize", let encodedRequest = url.pathComponents.last, let data = Data(base64URLEncoded: encodedRequest), let decodedRequest = String(data: data, encoding: .utf8) { return decodedRequest } // Alternative: com.yourcompany.wallet://authorize?request=BASE64_ENCODED_REQUEST if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let requestParam = components.queryItems?.first(where: { $0.name == "request" })?.value, let data = Data(base64URLEncoded: requestParam), let decodedRequest = String(data: data, encoding: .utf8) { return decodedRequest } return nil } } // Helper extension for base64 URL decoding extension Data { init?(base64URLEncoded string: String) { var base64 = string .replacingOccurrences(of: "-", with: "+") .replacingOccurrences(of: "_", with: "/") let paddingLength = (4 - base64.count % 4) % 4 base64.append(String(repeating: "=", count: paddingLength)) self.init(base64Encoded: base64) } } ``` Then, add an `onOpenURL` modifier in `ContentView` to handle incoming custom scheme URLs: ```swift title="ContentView.swift" .onOpenURL { url in viewModel.handleIncomingURL(url) } ``` If your app already handles other types of links, you'll need to update the `handleIncomingURL` method to support multiple link types. **Step 1: Add intent filter for custom scheme** Open your `AndroidManifest.xml` file and add a new intent filter for your custom scheme: ```xml title="AndroidManifest.xml" ``` **Step 2: Handle incoming intents** In your `MainActivity.kt` file, add handling for the custom scheme URLs: ```kotlin title="MainActivity.kt" class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Handle intent when activity is created handleIntent(intent) setContent { // ... existing UI code } } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) // Handle intent when activity is already running handleIntent(intent) } private fun handleIntent(intent: Intent?) { val data = intent?.data ?: return when (data.scheme) { "mdoc-openid4vp" -> { // Standard scheme handling (already implemented in tutorial) val authRequest = data.toString() navigateToOnlinePresentationScreen(authRequest) } "com.yourcompany.wallet" -> { // Custom scheme handling val authRequest = extractAuthorizationRequest(data) if (authRequest != null) { navigateToOnlinePresentationScreen(authRequest) } } } } private fun extractAuthorizationRequest(uri: Uri): String? { // Example: com.yourcompany.wallet://authorize/BASE64_ENCODED_REQUEST val encodedRequest = uri.lastPathSegment ?: uri.getQueryParameter("request") ?: return null return try { val decodedBytes = android.util.Base64.decode( encodedRequest.replace('-', '+').replace('_', '/'), android.util.Base64.URL_SAFE or android.util.Base64.NO_PADDING ) String(decodedBytes, Charsets.UTF_8) } catch (e: IllegalArgumentException) { null } } private fun navigateToOnlinePresentationScreen(requestUri: String) { // Navigate to your online presentation screen with the request URI // Using the same navigation pattern from the tutorial: // navController.navigate("onlinePresentation") // Pass requestUri as needed by your navigation implementation } } ``` **Step 1: Update scheme configuration** Open your `app.config.ts` file and update the scheme to your custom scheme: ```ts title="app.config.ts" export default ({ config }: ConfigContext): ExpoConfig => ({ // ... other config scheme: "com.yourcompany.wallet", // ... other config }); ``` **Step 2: Handle incoming URLs** In your `/app/_layout.tsx` file, add URL handling using Expo's Linking API: ```tsx title="/app/_layout.tsx" import { useEffect } from "react"; import * as Linking from "expo-linking"; import { useRouter } from "expo-router"; export default function RootLayout() { const router = useRouter(); useEffect(() => { // Handle URL when app is already open const subscription = Linking.addEventListener("url", ({ url }) => { handleIncomingURL(url); }); // Handle URL when app is opened from a closed state Linking.getInitialURL().then((url) => { if (url) { handleIncomingURL(url); } }); return () => { subscription.remove(); }; }, []); const handleIncomingURL = (url: string) => { const parsed = Linking.parse(url); // Handle custom scheme URLs if (parsed.scheme === "com.yourcompany.wallet" && parsed.hostname === "authorize") { const authRequest = extractAuthorizationRequest(url); if (authRequest) { router.replace({ pathname: "/online-presentation", params: { scannedValue: authRequest }, }); } } }; const extractAuthorizationRequest = (url: string): string | null => { try { const parsed = Linking.parse(url); // Extract from path: com.yourcompany.wallet://authorize/BASE64_ENCODED_REQUEST const encodedRequest = (parsed.path ? parsed.path.split("/").filter(Boolean).pop() : undefined) ?? parsed.queryParams?.request; if (!encodedRequest) return null; // Decode base64 URL-encoded request const base64 = encodedRequest .replace(/-/g, "+") .replace(/_/g, "/") .padEnd(encodedRequest.length + ((4 - (encodedRequest.length % 4)) % 4), "="); return atob(base64); } catch (error) { console.error("Failed to extract authorization request:", error); return null; } }; return ( ); } ``` **Step 3: Rebuild native projects** After changing the scheme configuration, regenerate the native projects: ```bash title="Regenerate native projects" yarn expo prebuild --clean ``` ### Claimed HTTPS scheme (App Links / Universal Links) [#claimed-https-scheme-app-links--universal-links] HTTPS schemes provide the most secure and reliable way to ensure only your specific app handles authorization requests from your domain. This requires coordination with the verifier to ensure the necessary files and settings are correctly implemented. **Step 1: Create the Apple App Site Association file** This step must be performed by the Verifier or the party controlling the domain used in the HTTPS scheme, as it requires hosting a specific file on the web server. If you control both the wallet and the verification service, you can complete this step yourself. Otherwise, you will need to coordinate with the verifier to ensure this file is created and hosted correctly. Create a file named `apple-app-site-association` (no file extension) with the following content: ```json title="apple-app-site-association" { "applinks": { "apps": [], "details": [ { "appID": "TEAM_ID.com.yourcompany.wallet", "paths": ["/wallet/*"] } ] } } ``` Replace: * `TEAM_ID` with your Apple Developer Team ID (found in your Apple Developer account). * `com.yourcompany.wallet` with your app's bundle identifier. * `/wallet/*` with the path pattern you want to handle (you can use multiple paths). **Step 2: Host the association file** Upload the file to your web server at: ``` https://yourdomain.com/.well-known/apple-app-site-association ``` Or directly at the root: ``` https://yourdomain.com/apple-app-site-association ``` Ensure: * The file is served with `Content-Type: application/json`. * The file is accessible via HTTPS without redirects. * No `.json` extension is added to the filename. **Step 3: Enable Associated Domains capability** 1. Open your project in Xcode. 2. Select your app target and navigate to **Signing & Capabilities**. 3. Select **+ Capability** and add **Associated Domains**. 4. Add your domain in the format: `applinks:yourdomain.com` **Step 4: Handle Universal Links** In your `ContentView.swift` file, add a handler for Universal Links. Add the following extension to your ViewModel: ```swift title="ContentView.swift" // MARK: Handle Universal Links extension ViewModel { func handleIncomingURL(_ url: URL) { guard url.scheme == "https" && url.host == "yourdomain.com" else { return } // Extract the authorization request from the URL // Example: https://yourdomain.com/wallet/authorize?request=BASE64_ENCODED_REQUEST if let requestString = extractAuthorizationRequest(from: url) { Task { await createOnlinePresentationSession(authorizationRequestURI: requestString) } navigationPath.append(NavigationState.onlinePresentation) } } func extractAuthorizationRequest(from url: URL) -> String? { // Verify path contains required components guard url.pathComponents.contains("wallet"), url.pathComponents.contains("authorize") else { return nil } // Extract request from query parameter if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), let requestParam = components.queryItems?.first(where: { $0.name == "request" })?.value, let data = Data(base64URLEncoded: requestParam), let decodedRequest = String(data: data, encoding: .utf8) { return decodedRequest } return nil } } // Helper extension for base64 URL decoding extension Data { init?(base64URLEncoded string: String) { var base64 = string .replacingOccurrences(of: "-", with: "+") .replacingOccurrences(of: "_", with: "/") let paddingLength = (4 - base64.count % 4) % 4 base64.append(String(repeating: "=", count: paddingLength)) self.init(base64Encoded: base64) } } ``` Then, add an `onOpenURL` modifier in `ContentView` to handle incoming universal links: ```swift title="ContentView.swift" .onOpenURL { url in viewModel.handleIncomingURL(url) } ``` If your app already handles other types of links, you'll need to update the `handleIncomingURL` method to support multiple link types. **Step 1: Create the Digital Asset Links file** This step must be performed by the Verifier or the party controlling the domain used in the HTTPS scheme, as it requires hosting a specific file on the web server. If you control both the wallet and the verification service, you can complete this step yourself. Otherwise, you will need to coordinate with the verifier to ensure this file is created and hosted correctly. Create a file named `assetlinks.json` with the following content: ```json title="assetlinks.json" [ { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.yourcompany.wallet", "sha256_cert_fingerprints": [ "YOUR_APP_SHA256_FINGERPRINT" ] } } ] ``` Replace: * `com.yourcompany.wallet` with your app's package name. * `YOUR_APP_SHA256_FINGERPRINT` with your app's SHA-256 certificate fingerprint. To get your SHA-256 fingerprint, run: ```bash title="Get SHA-256 fingerprint" keytool -list -v -keystore your-release-key.keystore ``` Or for debug builds: ```bash title="Get debug SHA-256 fingerprint" keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android ``` **Step 2: Host the association file** Upload the file to your web server at: ``` https://yourdomain.com/.well-known/assetlinks.json ``` Ensure: * The file is served with `Content-Type: application/json`. * The file is accessible via HTTPS without redirects. **Step 3: Add App Links intent filter** Open your `AndroidManifest.xml` and add an intent filter with `android:autoVerify="true"`: ```xml title="AndroidManifest.xml" ``` **Step 4: Handle App Links** Update your `MainActivity.kt` to handle App Links: ```kotlin title="MainActivity.kt" class MainActivity : ComponentActivity() { private fun handleIntent(intent: Intent?) { val data = intent?.data ?: return when { // Handle App Links (HTTPS URLs) data.scheme == "https" && data.host == "yourdomain.com" -> { handleAppLink(data) } // Handle custom schemes data.scheme == "mdoc-openid4vp" || data.scheme == "com.yourcompany.wallet" -> { handleCustomScheme(data) } } } private fun handleAppLink(uri: Uri) { // Example: https://yourdomain.com/wallet/authorize?request=BASE64_ENCODED_REQUEST if (uri.pathSegments.contains("wallet") && uri.pathSegments.contains("authorize")) { val encodedRequest = uri.getQueryParameter("request") ?: return val authRequest = extractAuthorizationRequest(encodedRequest) if (authRequest != null) { navigateToAuthScreen(authRequest) } } } private fun handleCustomScheme(uri: Uri) { // ... existing custom scheme handling } private fun extractAuthorizationRequest(encodedRequest: String): String? { return try { val decodedBytes = android.util.Base64.decode( encodedRequest.replace('-', '+').replace('_', '/'), android.util.Base64.URL_SAFE or android.util.Base64.NO_PADDING ) String(decodedBytes, Charsets.UTF_8) } catch (e: IllegalArgumentException) { null } } ``` **Step 5: Verify App Links** After installing your app, verify that App Links are working: ```bash title="Verify App Links" adb shell am start -a android.intent.action.VIEW -d "https://yourdomain.com/wallet/authorize?request=test" ``` You can also check the verification status: ```bash title="Check verification status" adb shell pm get-app-links com.yourcompany.wallet ``` For React Native with Expo, configuring Universal Links (iOS) and App Links (Android) requires additional setup in the app configuration. **Step 1: Create association files** This step must be performed by the Verifier or the party controlling the domain used in the HTTPS scheme, as it requires hosting a specific file on the web server. If you control both the wallet and the verification service, you can complete this step yourself. Otherwise, you will need to coordinate with the verifier to ensure this file is created and hosted correctly. Follow the instructions in the iOS and Android tabs to create and host: * `apple-app-site-association` file at `https://yourdomain.com/.well-known/apple-app-site-association` * `assetlinks.json` file at `https://yourdomain.com/.well-known/assetlinks.json` **Step 2: Configure app for Universal Links / App Links** Update your `app.config.ts`: ```ts title="app.config.ts" export default ({ config }: ConfigContext): ExpoConfig => ({ // ... other config ios: { bundleIdentifier: "com.yourcompany.wallet", associatedDomains: ["applinks:yourdomain.com"], // ... other iOS config }, android: { package: "com.yourcompany.wallet", intentFilters: [ { action: "VIEW", autoVerify: true, data: [ { scheme: "https", host: "yourdomain.com", pathPrefix: "/wallet", }, ], category: ["BROWSABLE", "DEFAULT"], }, ], // ... other Android config }, // ... other config }); ``` **Step 3: Handle Universal Links / App Links** Update your `/app/_layout.tsx` to handle HTTPS URLs: ```tsx title="/app/_layout.tsx" import { useEffect } from "react"; import * as Linking from "expo-linking"; import { useRouter } from "expo-router"; export default function RootLayout() { const router = useRouter(); useEffect(() => { const subscription = Linking.addEventListener("url", ({ url }) => { handleIncomingURL(url); }); Linking.getInitialURL().then((url) => { if (url) { handleIncomingURL(url); } }); return () => { subscription.remove(); }; }, []); const handleIncomingURL = (url: string) => { const parsed = Linking.parse(url); // Handle Universal Links / App Links (HTTPS) if (parsed.scheme === "https" && parsed.hostname === "yourdomain.com") { handleAppLink(url); return; } // Handle custom schemes if ( parsed.scheme === "mdoc-openid4vp" || parsed.scheme === "com.yourcompany.wallet" ) { handleCustomScheme(url); return; } }; const handleAppLink = (url: string) => { const parsed = Linking.parse(url); // Example: https://yourdomain.com/wallet/authorize?request=BASE64_ENCODED_REQUEST if (parsed.path?.includes("/wallet/authorize")) { const encodedRequest = parsed.queryParams?.request as string; if (encodedRequest) { const authRequest = extractAuthorizationRequest(encodedRequest); if (authRequest) { router.replace({ pathname: "/online-presentation", params: { scannedValue: authRequest }, }); } } } }; const handleCustomScheme = (url: string) => { // ... existing custom scheme handling }; const extractAuthorizationRequest = (encodedRequest: string): string | null => { try { const base64 = encodedRequest .replace(/-/g, "+") .replace(/_/g, "/") .padEnd(encodedRequest.length + ((4 - (encodedRequest.length % 4)) % 4), "="); return atob(base64); } catch (error) { console.error("Failed to decode authorization request:", error); return null; } }; return ( ); } ``` **Step 4: Rebuild native projects** After updating the configuration, regenerate the native projects: ```bash title="Regenerate native projects" yarn expo prebuild --clean ``` **Step 5: Build and test** For iOS, you must test Universal Links on a physical device (not simulator) with a production or ad-hoc build. For Android, install the app and verify App Links as described in the Android tab above. ## Testing your URI scheme implementation [#testing-your-uri-scheme-implementation] After configuring your app to handle different URI schemes, test each implementation to ensure it works correctly. ### Testing with QR codes [#testing-with-qr-codes] Create an authorization request and generate QR codes for each URI scheme type you support: 1. **ISO 18013-7 scheme**: `mdoc-openid4vp://authorize?request=ey...` 2. **Custom scheme**: `com.yourcompany.wallet://authorize?request=ey...` 3. **HTTPS scheme**: `https://yourdomain.com/wallet/authorize?request=ey...` You can use online QR code generators or create them programmatically for testing. ### Testing with deep links [#testing-with-deep-links] A deep link works the same way as a QR code — pass the full authorization request URI directly. For iOS, use the following command to test on a simulator: ```bash title="Test iOS deep link" xcrun simctl openurl booted "com.yourcompany.wallet://authorize?request=ey..." ``` For Android, use: ```bash title="Test Android deep link" adb shell am start -a android.intent.action.VIEW -d "com.yourcompany.wallet://authorize?request=ey..." ``` ### Testing Universal Links / App Links [#testing-universal-links--app-links] For iOS Universal Links, you must test on a physical device with a production or ad-hoc build. Links opened in Safari should open your app. For Android App Links, use: ```bash title="Test Android App Link" adb shell am start -a android.intent.action.VIEW -d "https://yourdomain.com/wallet/authorize?request=ey..." ``` ## Common issues and troubleshooting [#common-issues-and-troubleshooting] ### App doesn't open when scanning QR code or tapping link [#app-doesnt-open-when-scanning-qr-code-or-tapping-link] **Possible causes:** * URI scheme not properly registered in the app configuration. * For Universal Links/App Links, association files not properly hosted or accessible. * For Universal Links/App Links, domain not added to associated domains / intent filters. **Solutions:** * Verify URL scheme registration in your app configuration. * Test the association file URLs directly in a browser to ensure they're accessible. * Check that the association file content matches your app's bundle ID/package name and certificate. * For iOS, verify Associated Domains capability is enabled and domains are correctly listed. * For Android, verify `android:autoVerify="true"` is set and check verification status with `adb shell pm get-app-links`. ### Wrong app opens when multiple wallet apps are installed [#wrong-app-opens-when-multiple-wallet-apps-are-installed] **Possible causes:** * Multiple apps registered for the same URI scheme (common with `mdoc-openid4vp://`). * Association files not properly verified (for Universal Links/App Links). **Solutions:** * Use a unique custom scheme or domain-verified HTTPS scheme. * For Universal Links/App Links, ensure association files are correctly configured and verified. * Test on a clean device or uninstall competing apps during testing. ### Encoded request fails to decode [#encoded-request-fails-to-decode] **Possible causes:** * Incorrect base64 URL encoding/decoding. * Request parameter not properly extracted from URL. **Solutions:** * Ensure you're using base64 URL encoding (not standard base64) with `-` and `_` instead of `+` and `/`. * Verify padding is handled correctly when decoding. * Log the encoded and decoded values to debug the transformation. ## Related resources [#related-resources] * [iOS Universal Links documentation](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) * [Android App Links documentation](https://developer.android.com/training/app-links) * [OpenID for Verifiable Presentations (OID4VP)](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) * [OAuth 2.0 for Native Apps (RFC 8252)](https://datatracker.ietf.org/doc/html/rfc8252) # Handling verifier authentication URL: /docs/holding/remote-presentation-guides/verifier-authentication Description: Establish and inspect how a remote verifier was authenticated so users can decide whether to share credentials with an unauthenticated verifier ## Overview [#overview] When a verifier requests credentials remotely over OpenID for Verifiable Presentations (OID4VP), your holder application can establish whether the verifier proved its identity before any credential data is shared. This lets you show the user who is asking for their data and how that identity was established, and lets you refuse to proceed with a verifier that cannot be trusted. In a remote presentation, the verifier signs the authorization request. The Holder SDK validates that signature when it creates the presentation session, and reports how the verifier was authenticated. Your application controls how strict that validation must be, and reads the outcome to inform the user. This guide covers remote (OID4VP / ISO/IEC 18013-7) verifier authentication, where the verifier signs the authorization request and trust is established when the session is created. Proximity (ISO/IEC 18013-5) presentations authenticate the verifier through a signed device request instead, which ISO/IEC 18013-5 calls *mDoc Reader Authentication*. The `verifierAuthenticationResult` described in the [proximity guide](/docs/holding/proximity-presentation-guides/verifier-authentication) does not apply to remote presentations; the remote equivalent is the `requireTrustedVerifier` option and the `verifiedBy` result described below. ## Prerequisites [#prerequisites] This guide builds on the [Remote Presentation tutorial](/docs/holding/remote-presentation-tutorial). Complete that tutorial first so you have a working holder application that can create an online presentation session from an authorization request, then return here to add verifier authentication handling. ## Understanding remote verifier authentication [#understanding-remote-verifier-authentication] Under ISO/IEC 18013-7:2025, the authorization request is delivered as a signed request object (a JWT) using the `x509_san_dns` client identifier scheme. The verifier's `client_id` must be a DNS name that matches a `dNSName` Subject Alternative Name in the certificate that signed the request. When you create the session, the SDK validates the request signature and then reports how the verifier was authenticated through a `verifiedBy` result with one of two outcomes: * **Certificate**: The request signature was validated against a trusted verifier root certificate configured on the device. This is the stronger outcome, because the verifier's certificate is explicitly trusted ahead of time. The result carries the certificate's common name. * **Domain**: The request signature was validated using the verifier's published client metadata, bound to its DNS name through the `x509_san_dns` scheme, rather than against a pre-configured trusted certificate. The result carries the `client_id` from the request. You control which of these is acceptable with the `requireTrustedVerifier` option when you create the session: * When `requireTrustedVerifier` is `false` (the default), the SDK first attempts validation against the stored trusted verifier certificates. If that fails, it falls back to resolving the verifier's client metadata and validating against the keys published there. A successful session is reported as either `Certificate` or `Domain` accordingly. * When `requireTrustedVerifier` is `true`, the SDK only accepts a request that validates against a stored trusted verifier certificate. If no trusted certificate matches, session creation fails rather than falling back to domain validation. If validation fails, session creation reports an error: validation against a trusted certificate failing surfaces as an "invalid authorization request, verified by certificate" error, and domain validation failing surfaces as an "invalid authorization request, verified by domain" error. ## Adding trusted verifier certificates [#adding-trusted-verifier-certificates] A `Certificate` result, and the `requireTrustedVerifier` option, both depend on trusted verifier root certificates stored on the device. Your application provisions these certificates through the SDK. The same trusted verifier certificate store is used for both remote and [proximity](/docs/holding/proximity-presentation-guides/verifier-authentication) presentations. Certificates are supplied as PEM-encoded or Base64-encoded DER strings. Adding a certificate is idempotent: adding the same certificate again returns the identifier of the existing entry rather than creating a duplicate. Use [`addTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/addtrustedverifiercertificates\(certificates:\)), [`getTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/gettrustedverifiercertificates\(\)), and [`deleteTrustedVerifierCertificate`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/deletetrustedverifiercertificate\(certificateid:\)): ```swift title="CertificateSettings.swift" let mobileCredentialHolder = MobileCredentialHolder.shared // Add one or more trusted verifier certificates (PEM or Base64 DER). let addedIds = try mobileCredentialHolder.addTrustedVerifierCertificates( certificates: [verifierCertificatePem] ) // List the stored trusted verifier certificates. let stored: [TrustedCertificate] = try mobileCredentialHolder.getTrustedVerifierCertificates() // Remove one by its identifier. try mobileCredentialHolder.deleteTrustedVerifierCertificate(certificateId: addedIds[0]) ``` Each stored certificate is a [`TrustedCertificate`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/trustedcertificate) carrying its `id`, `pem`, and `commonName`. Use [`addTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/add-trusted-verifier-certificates.html), [`getTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/get-trusted-verifier-certificates.html), and [`deleteTrustedVerifierCertificate`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/delete-trusted-verifier-certificate.html): ```kotlin title="CertificateSettings.kt" val mobileCredentialHolder = MobileCredentialHolder.getInstance() // Add one or more trusted verifier certificates (PEM or Base64 DER). val addedIds: List = mobileCredentialHolder.addTrustedVerifierCertificates( listOf(verifierCertificatePem) ) // List the stored trusted verifier certificates. val stored: List = mobileCredentialHolder.getTrustedVerifierCertificates() // Remove one by its identifier. mobileCredentialHolder.deleteTrustedVerifierCertificate(addedIds.first()) ``` Each stored certificate is a `TrustedCertificate` carrying its `id`, `pem`, and `commonName`. Use [`addTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/addTrustedVerifierCertificates.html), [`getTrustedVerifierCertificates`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/getTrustedVerifierCertificates.html), and [`deleteTrustedVerifierCertificate`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/deleteTrustedVerifierCertificate.html): ```ts title="trustedVerifierCertificates.ts" import { addTrustedVerifierCertificates, getTrustedVerifierCertificates, deleteTrustedVerifierCertificate, } from "@mattrglobal/mobile-credential-holder-react-native"; // Add one or more trusted verifier certificates (PEM or Base64 DER). const result = await addTrustedVerifierCertificates([verifierCertificatePem]); if (result.isErr()) { console.error("Failed to add trusted verifier certificates:", result.error); return; } const addedIds = result.value; // List the stored trusted verifier certificates. const stored = await getTrustedVerifierCertificates(); // Remove one by its identifier. await deleteTrustedVerifierCertificate(addedIds[0]); ``` Each stored certificate is a [`TrustedVerifierCertificate`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/TrustedVerifierCertificate.html) carrying its `id`, `pem`, and `commonName`. ## Creating the session and inspecting the result [#creating-the-session-and-inspecting-the-result] Create the session as in the [Remote Presentation tutorial](/docs/holding/remote-presentation-tutorial#step-2-create-an-online-presentation-session), choosing the value of `requireTrustedVerifier` that fits your trust policy, then read `verifiedBy` from the returned session before presenting the consent UI. Pass `requireTrustedVerifier` to [`createOnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/createonlinepresentationsession\(authorisationrequesturi:requiretrustedverifier:\)) and switch over the `verifiedBy` property of the returned [`OnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/onlinepresentationsession): ```swift title="ViewModel.swift" func createOnlinePresentationSession(authorizationRequestUri: String) async { do { let session = try await mobileCredentialHolder.createOnlinePresentationSession( authorizationRequestUri: authorizationRequestUri, requireTrustedVerifier: false ) switch session.verifiedBy { case .certificate(let commonName): // Validated against a trusted verifier root certificate. print("Verified by certificate: \(commonName)") case .domain(let clientId): // Validated against the verifier's published client metadata. print("Verified by domain: \(clientId)") } // Continue with session.matchedCredentials and your consent UI. } catch MobileCredentialHolderError.invalidAuthorizationRequestVerifiedByCertificate { // requireTrustedVerifier was true and no trusted certificate matched. } catch MobileCredentialHolderError.invalidAuthorizationRequestVerifiedByDomain { // The request could not be validated against the verifier's domain. } catch { // Handle other errors } } ``` Pass `requireTrustedVerifier` to [`createOnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/create-online-presentation-session.html) and match over the `verifiedBy` property of the returned [`OnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.onlinepresentation/-online-presentation-session/index.html): ```kotlin title="OnlinePresentationViewModel.kt" try { val session = MobileCredentialHolder.getInstance().createOnlinePresentationSession( authorizationRequestUri = requestUri, requireTrustedVerifier = false ) when (val verifiedBy = session.verifiedBy) { is VerifiedBy.Certificate -> // Validated against a trusted verifier root certificate. Log.i(TAG, "Verified by certificate: ${verifiedBy.commonName}") is VerifiedBy.Domain -> // Validated against the verifier's published client metadata. Log.i(TAG, "Verified by domain: ${verifiedBy.domain}") } // Continue with session.getMatchedCredentials() and your consent UI. } catch (e: HolderException.InvalidAuthorizationRequestVerifiedByCertificateException) { // requireTrustedVerifier was true and no trusted certificate matched. } catch (e: Throwable) { // Handle other errors } ``` See [`VerifiedBy`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.onlinepresentation/-verified-by/index.html) for the associated values. Pass `requireTrustedVerifier` to [`createOnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/createOnlinePresentationSession.html) and read the `verifiedBy` property of the returned [`OnlinePresentationSession`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/OnlinePresentationSession.html). The function returns a `Result`, so the untrusted case is an error value rather than a thrown exception: ```ts title="usePresentations.ts" const result = await createOnlinePresentationSession({ authorizationRequestUri, requireTrustedVerifier: false, }); if (result.isErr()) { const { error } = result; if (error.type === MobileCredentialHolderErrorType.InvalidAuthorizationRequestVerifiedByCertificate) { // requireTrustedVerifier was true and no trusted certificate matched. } // Handle other errors return; } const session = result.value; switch (session.verifiedBy.type) { case OnlinePresentationSessionVerifiedByType.Certificate: // Validated against a trusted verifier root certificate. console.log(`Verified by certificate: ${session.verifiedBy.value}`); break; case OnlinePresentationSessionVerifiedByType.Domain: // Validated against the verifier's published client metadata. console.log(`Verified by domain: ${session.verifiedBy.value}`); break; } // Continue with session.matchedCredentials and your consent UI. ``` See [`OnlinePresentationSessionVerifiedBy`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/OnlinePresentationSessionVerifiedBy.html) for the result shape. ## What the outcomes mean [#what-the-outcomes-mean] The `verifiedBy` result describes how the verifier's identity was established. What each outcome tells your application: * **Certificate**: The request signature was validated against a trusted verifier root certificate stored on the device. The result carries the certificate's common name. * **Domain**: The request signature was validated through the verifier's published client metadata, bound to its DNS name through the `x509_san_dns` scheme, rather than against a pre-configured certificate. The result carries the `client_id` from the request. * **Validation failed**: Session creation did not succeed, and no session is returned. How an application uses this is a product and trust-framework decision that the SDK does not make for you. The `requireTrustedVerifier` option and the `verifiedBy` result give you two complementary controls, and different implementations combine them differently depending on their ecosystem and risk tolerance. For example, an application might: * set `requireTrustedVerifier` to `true` so that only pre-vetted verifiers can create a session, and domain-only verifiers are rejected at session creation; * leave `requireTrustedVerifier` as `false` and accept both outcomes, recording how trust was established for audit; * present the `verifiedBy` result to the user and let them decide whether to continue; * proceed automatically for a `Certificate` result while requiring explicit user confirmation for a `Domain` result. Whichever approach you take, you have the outcome before presenting any data, so you can carry it into your flow and respond only for the credentials involved, as shown in the [Remote Presentation tutorial](/docs/holding/remote-presentation-tutorial#step-4-send-response). A `Domain` result means the request was validly signed and bound to the verifier's DNS name, but it does not mean the verifier has been vetted against a trust framework ahead of time. Whether that distinction matters for a given interaction depends on your ecosystem. ## Testing remote verifier authentication [#testing-remote-verifier-authentication] To exercise each outcome: 1. Claim a credential in your holder application. 2. Present to a verifier whose request is signed by a certificate trusted on the device, and confirm the session is reported as verified by certificate with the expected common name. 3. Present to a verifier whose request validates only through its published client metadata, and confirm the session is reported as verified by domain with the expected `client_id`. 4. Set `requireTrustedVerifier` to `true` and repeat step 3, confirming that session creation now fails with an "invalid authorization request, verified by certificate" error. # mDocs remote web presentation journey pattern URL: /docs/holding/remote-presentation-journey-patterns/mobile This journey pattern assumes that the wallet is presenting a credential to a verifier using MATTR verification capabilities. However, the same pattern can be applied to any ISO/IEC 18013-7 compliant verifier. This journey pattern is used to verify an mDoc remotely by presenting it to an app installed on the same mobile device as the digital wallet, as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). ## Overview [#overview] * **Issuance channel**: Remote, unsupervised * **Device/s**: Same-device * **Formats**: mDocs * **Information assurance level**: High * **Identity assurance level**: High ## Journey flow [#journey-flow] mDocs Mobile Verification ## Architecture [#architecture] Remote verification mobile architecture ### Interacting with the mobile application [#interacting-with-the-mobile-application] The user accesses a mobile app that embeds the MATTR Pi Verifier Mobile SDK. The app initiates and handles the entire verification flow on the same device. ### Requesting a credential for verification [#requesting-a-credential-for-verification] The Verifier Mobile SDK sends a verification request to a configured MATTR VII verifier tenant, defining: * The credentials and claims required * The supported interaction mode (same-device) The MATTR VII verifier tenant is configured with: * Which apps or domains can issue verification requests * The workflows it supports (same-device and/or cross-device) * The protocols it supports (e.g. OID4VP, Apple’s Verify with Wallet API) * Which wallet applications it can invoke on the same device The verifier tenant responds with a custom URI or universal link. The Verifier Mobile SDK uses this to launch the wallet app directly. ### Presenting request details to the user [#presenting-request-details-to-the-user] The wallet retrieves the presentation request and displays: * The credentials requested * The claims that will be shared * Whether the relying party is trusted by the Digital Trust Service * Which of the user’s credentials match the request The user authenticates and consents to share the requested information. ### Verifying the credential [#verifying-the-credential] The MATTR VII verifier tenant verifies the credential by checking: * Validating the digital signature to confirm the data has not been tampered with * Checking that the credential has not been revoked or suspended, using a revocation list (if applicable) * Verifying that the credential is currently valid, based on its “valid from” and “valid until” dates * Ensuring the credential was issued by a trusted issuer, based on information retrieved from a Digital Trust Service The issuer of the credential is not informed that the presentation has occurred. No data about the verifier, the context of use, or the interaction itself is shared with the issuer. The only interaction with the issuer is a potential call to an online revocation endpoint, if revocation checking is required. ### Displaying verification results [#displaying-verification-results] Once verification is complete, the wallet app redirects the user back to the mobile application using the provided redirect URI. The Verifier Mobile SDK receives the result and displays it within the app interface. The MATTR VII verifier tenant can also be configured to share the verification results with a configured back-end rather than the front-end directly. # mDocs remote web presentation journey pattern URL: /docs/holding/remote-presentation-journey-patterns/web This journey pattern assumes that the wallet is presenting a credential to a verifier using MATTR verification capabilities. However, the same pattern can be applied to any ISO/IEC 18013-7 compliant verifier. This journey pattern is used to verify an mDoc remotely via an [online verification workflow](/docs/verification/remote-overview), as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). ## Overview [#overview] * **Issuance channel**: Remote, unsupervised * **Device/s**: Same-device / Cross-device * **Formats**: mDocs * **Information assurance level**: High * **Identity assurance level**: High ## Journey flow - Same-device [#journey-flow---same-device] mDocs Web Verification Same-device ## Architecture - Same-device [#architecture---same-device] Remote web verification same-device architecture ### Interacting with the website [#interacting-with-the-website] The user accesses a website using a mobile browser on the same device that holds their wallet app. ### Requesting a credential for verification [#requesting-a-credential-for-verification] On the website, the user begins an interaction that requires them to present a credential. The MATTR Pi Verifier Web SDK, embedded in the web application, initiates the verification request by sending it to a configured MATTR VII verifier tenant. This request defines: * The credentials and claims required * The supported interaction modes (same-device, in this case) The MATTR VII verifier tenant is configured with: * The domains it can accept requests from * The workflows it supports (e.g. same-device and/or cross-device) * The supported protocols (e.g. Digital Credentials API and/or OID4VP) * The wallet applications it can interact with * How to invoke these wallet applications on the same device Based on this configuration, the MATTR VII verifier tenant identifies that the user is using a mobile browser and returns a universal link or custom URI that can invoke the wallet app. The Verifier Web SDK uses this link to redirect the user to their wallet application. ### Presenting request details to the user [#presenting-request-details-to-the-user] Once the wallet is launched, it authenticates the user and retrieves the verification request from the MATTR VII verifier tenant. The user is shown: * The credentials being requested * The claims required from those credentials * Whether the relying party is trusted and authorized by the Digital Trust Service * Which of the user’s credentials match the request The user can then review and consent to sharing the information. ### Verifying the credential [#verifying-the-credential] The MATTR VII verifier tenant verifies the presentation by checking: * That the credential has not been tampered with * That it has not been revoked or suspended * That it has not expired * That it was issued by a trusted issuer, validated via the configured Digital Trust Service ### Displaying verification results [#displaying-verification-results] After the verification is complete, the wallet app redirects the user back to their mobile browser, returning them to the original web application using the previously provided redirect URl. The Verifier Web SDK receives the verification result and renders it in the browser, allowing the user to view the outcome and continue their interaction. The issuer of the credential is not informed that the presentation has occurred. No data about the verifier, the context of use, or the interaction itself is shared with the issuer. The only interaction with the issuer is a potential call to an online revocation endpoint, if revocation checking is required. The MATTR VII verifier tenant can also be configured to return the verification result to a secure back-end service instead of the front-end, depending on implementation needs. ## Journey flow - Cross-device [#journey-flow---cross-device] mDocs Web Verification Cross-device ## Architecture - Cross-device [#architecture---cross-device] Remote verification mobile architecture ### Interacting with the website [#interacting-with-the-website-1] The user accesses a website using a web browser on their desktop. ### Requesting a credential for verification [#requesting-a-credential-for-verification-1] On the website, the user begins an interaction that requires them to present a credential. The MATTR Pi Verifier Web SDK, embedded in the web application, initiates the verification request by sending it to a configured MATTR VII verifier tenant. This request defines: * The credentials and claims required * The supported interaction modes (same-device, in this case) The MATTR VII verifier tenant is configured with: * The domains it can accept requests from * The workflows it supports (e.g. same-device and/or cross-device) * The supported protocols (e.g. Digital Credentials API and/or OID4VP) * The wallet applications it can interact with * How to invoke these wallet applications on the same device Based on this configuration, the MATTR VII verifier tenant returns a link. The Verifier Web SDK recognizes Samantha is using a desktop browser and renders this link as a QR code on the webpage. The user scans the QR code using a mobile device that has a compatible digital wallet installed. This action invokes the wallet app with the verification request. ### Presenting request details to the user [#presenting-request-details-to-the-user-1] Once the wallet is launched, it authenticates the user and retrieves the verification request from the MATTR VII verifier tenant. The user is shown: * The credentials being requested * The claims required from those credentials * Whether the relying party is trusted and authorized by the Digital Trust Service * Which of the user’s credentials match the request The user can then review and consent to sharing the information. ### Verifying the credential [#verifying-the-credential-1] The MATTR VII verifier tenant verifies the presentation by checking: * That the credential has not been tampered with * That it has not been revoked or suspended * That it has not expired * That it was issued by a trusted issuer, validated via the configured Digital Trust Service ### Displaying verification results [#displaying-verification-results-1] The MATTR VII verifier tenant shares the verification results with the Verifier Web SDK. These results are displayed to the user in the browser, allowing them to continue their interaction The issuer of the credential is not informed that the presentation has occurred. No data about the verifier, the context of use, or the interaction itself is shared with the issuer. The only interaction with the issuer is a potential call to an online revocation endpoint, if revocation checking is required. The MATTR VII verifier tenant can also be configured to return the verification result to a secure back-end service instead of the front-end, depending on implementation needs. # Configure activity logs URL: /docs/holding/sdk-operations/activity-log Description: Learn how to configure activity logging in the MATTR Holder SDKs to record credential and presentation events for display in your wallet application. The MATTR Holder SDKs include an activity logging system that records user-facing events such as credential additions, removals, and presentations. Unlike [SDK logging](/docs/holding/sdk-operations/sdk-logging), which captures internal diagnostic information for developers, activity logs are designed to power end-user features in your wallet application, such as a credential usage history. ## How activity logs differ from SDK logs [#how-activity-logs-differ-from-sdk-logs] The activity log and SDK log serve different purposes and audiences, even though they both capture events related to the SDK's operation. The following table summarizes the key differences: | Feature | Activity logs | SDK logs | | ----------------- | ------------------------------------ | -------------------------------------------------- | | **Purpose** | User-facing credential usage history | Developer diagnostics and debugging | | **Audience** | End users of your wallet application | Developers integrating the SDK | | **Content** | Credential and presentation events | Internal SDK operations, errors, and state changes | | **Configuration** | `activityLogConfiguration` | `loggerConfiguration` | ## Privacy considerations [#privacy-considerations] Activity logs are stored **only on the user's device**. They are never shared with issuers, verifiers, or any external party as part of credential issuance or presentation interactions. Activity logs are intended to give users visibility into how their credentials have been used. This enables you to build transparency features into your wallet application, such as: * Showing the user when and where a credential was presented. * Displaying a history of credential additions and removals. * Allowing users to review and clear their activity history. Because this data stays on-device, it supports user sovereignty and trust without creating additional privacy risks. ## What events are recorded [#what-events-are-recorded] The activity log records two types of events: * **Credential events**: Recorded when a credential is added to or removed from storage. Each event includes the credential identifier, document type, issuer details, and optional branding information. * **Presentation events**: Recorded when a credential is presented to a verifier, or when a presentation request is declined. Each event includes details about what was requested, what was shared, and the type of session (proximity or online). Where available, the SDK also records the identity of the requesting verifier based on a trusted verifier certificate or, for online sessions, the verifier's domain. Each activity log entry contains a timestamp and one or more events that occurred at that point in time. ## Enable activity logging at initialization [#enable-activity-logging-at-initialization] Activity logging is configured by passing an `ActivityLogConfiguration` object to the SDK's `initialize` method (this is separate from the `loggerConfiguration` used for SDK diagnostic logs). ```swift title="Enable activity logging during initialization" try await mobileCredentialHolder.initialize( activityLogConfiguration: ActivityLogConfiguration( // [!code highlight] isEnabled: true // [!code highlight] ) // [!code highlight] ) ``` * `isEnabled`: Set to `true` to start recording activity log events immediately after initialization. Defaults to `true`. Refer to the [`ActivityLogConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/activitylogconfiguration) reference documentation for additional details. ```kotlin title="Enable activity logging during initialization" mobileCredentialHolder.initialize( activityLogConfiguration = ActivityLogConfiguration( isEnabled = true ), // ... ) ``` * `isEnabled`: Set to `true` to start recording activity log events immediately after initialization. Defaults to `true`. Refer to the [`ActivityLogConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.activitylog.dto/-activity-log-configuration/index.html) reference documentation for additional details. Coming soon... ## Toggle activity logging at runtime [#toggle-activity-logging-at-runtime] You can enable or disable activity logging after initialization without reinitializing the SDK. This overrides the value set in `activityLogConfiguration` during initialization, and is useful for allowing users to opt in or out of activity tracking from your application's settings. ```swift title="Toggle activity logging" // Enable activity logging try mobileCredentialHolder.setActivityLogEnabled(true) // Disable activity logging try mobileCredentialHolder.setActivityLogEnabled(false) // Check if activity logging is currently enabled let isEnabled = mobileCredentialHolder.isActivityLogRecordingEnabled ``` * [`setActivityLogEnabled(_:)`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/setactivitylogenabled\(_:\)): Enable or disable activity log recording. * [`isActivityLogRecordingEnabled`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/isactivitylogrecordingenabled/): A Boolean indicating whether activity log recording is currently enabled. ```kotlin title="Toggle activity logging" // Enable activity logging mobileCredentialHolder.setActivityLogEnabled(true) // Disable activity logging mobileCredentialHolder.setActivityLogEnabled(false) // Check if activity logging is currently enabled val isEnabled = mobileCredentialHolder.isActivityLogRecordingEnabled() ``` * [`setActivityLogEnabled`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/set-activity-log-enabled.html): Enable or disable activity log recording. * [`isActivityLogRecordingEnabled`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/is-activity-log-recording-enabled.html): Returns a Boolean indicating whether activity log recording is currently enabled. Coming soon... ## Retrieve activity log entries [#retrieve-activity-log-entries] You can retrieve recorded activity log entries. Each entry contains a timestamp and a list of events that occurred at that point in time. Events are either credential events (additions or removals) or presentation events (shares or declines). ```swift title="Retrieve activity log entries" // Get the first 50 entries let entries = try mobileCredentialHolder.getActivityLog(count: 50, offset: 0) for entry in entries { print("Entry \(entry.id) at \(entry.dateTime)") for event in entry.events { switch event { case .credential(let credentialEvent): print(" Credential event: \(credentialEvent.eventType) - \(credentialEvent.docType)") case .presentation(let presentationEvent): print(" Presentation event: \(presentationEvent.eventType)") } } } ``` The [`getActivityLog(count:offset:)`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/getactivitylog\(count:offset:\)) method returns an array of [`ActivityLogEntry`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/activitylogentry/) objects. Each entry contains: * `id`: A unique identifier for the entry. * `dateTime`: The timestamp when the entry was recorded. * `events`: An array of [`ActivityLogEvent`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/activitylogevent) values, which are either [`credential`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/activitylogcredentialevent) (representing credential additions or removals) or [`presentation`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/activitylogpresentationevent) (representing presentation events) events. Parameters: * `count`: The maximum number of entries to return. * `offset`: The number of entries to skip (for pagination). ```kotlin title="Retrieve activity log entries" // Get the first 50 entries val entries = mobileCredentialHolder.getActivityLog(count = 50, offset = 0) entries.forEach { entry -> Log.d("Test", "Entry ${entry.id} at ${entry.dateTime}") entry.events.forEach { event -> when (event) { is ActivityLogCredentialEvent -> Log.d("Test", "Credential event: ${event.eventType} - ${event.docType}") is ActivityLogPresentationEvent -> Log.d("Test", "Presentation event: ${event.eventType}") } } } ``` The [`getActivityLog`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/get-activity-log.html) method returns a list of [`ActivityLogEntry`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.activitylog.dto/-activity-log-entry/index.html) objects. Each entry contains: * `id`: A unique identifier for the entry. * `dateTime`: The timestamp when the entry was recorded. * `events`: A list of [`ActivityLogEvent`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.activitylog.dto/-activity-log-event/index.html) instances, which are either [`ActivityLogCredentialEvent`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.activitylog.dto/-activity-log-credential-event/index.html) (representing credential additions or removals) or [`ActivityLogPresentationEvent`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.activitylog.dto/-activity-log-presentation-event/index.html) (representing presentation events). Parameters: * `count`: The maximum number of entries to return. Defaults to `100`. * `offset`: The number of entries to skip (for pagination). Defaults to `0`. Coming soon. ## Clear the activity log [#clear-the-activity-log] You can clear all activity log entries from the device. This permanently deletes all recorded events and cannot be undone. You might expose this option to users as a way to reset their activity history. ```swift title="Clear all activity log entries" try mobileCredentialHolder.clearActivityLog() ``` The [`clearActivityLog()`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/clearactivitylog\(\)) method removes all activity log entries from the device. ```kotlin title="Clear all activity log entries" mobileCredentialHolder.clearActivityLog() ``` The [`clearActivityLog`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/clear-activity-log.html) method removes all activity log entries from the device. Coming soon... # Configure SDK logging URL: /docs/holding/sdk-operations/sdk-logging Description: Learn how to configure logging in the MATTR Holder SDKs, including log levels, callback handlers, and accessing log files across iOS, Android, and React Native platforms. The MATTR Holder SDKs include a built-in logging system that records internal SDK operations. This is useful for debugging integration issues, monitoring SDK behavior, and capturing diagnostic information during development and testing. By default, SDK logs are stored locally on the device. The SDK itself does not transmit logs to any external service, although your application can choose to export or forward log events if you register a callback handler. ## What information the SDK can log [#what-information-the-sdk-can-log] The SDK can log information about its internal operations, including errors and warnings encountered during SDK operations. All log entries include the log level and a descriptive message. This information helps you diagnose issues and understand how the SDK operates within your application. ## How SDK logs differ from activity logs [#how-sdk-logs-differ-from-activity-logs] The SDK provides two types of logs: SDK logs and activity logs. While both are valuable for monitoring and diagnostics, they serve different purposes and audiences. | Feature | SDK logs | Activity logs | | ----------------- | -------------------------------------------------- | ------------------------------------ | | **Purpose** | Developer diagnostics and debugging | User-facing credential usage history | | **Audience** | Developers integrating the SDK | End users of your wallet application | | **Content** | Internal SDK operations, errors, and state changes | Credential and presentation events | | **Configuration** | `loggerConfiguration` | `activityLogConfiguration` | To learn about activity logs, see [Configure activity logs](/docs/holding/sdk-operations/activity-log). ## Log levels [#log-levels] The SDK supports the following log levels, ordered from most to least verbose: | Level | Description | | --------- | -------------------------------------------------------------------------- | | `Verbose` | Fine-grained informational events, most detailed output. | | `Debug` | Detailed information useful during development. | | `Info` | General informational messages about SDK operations. | | `Warning` | Potentially harmful situations or unexpected behavior (`Warn` in Android). | | `Error` | Error events that might still allow the SDK to continue running. | | `Assert` | Severe error events that indicate a critical failure (Android only). | | `Off` | Disables logging entirely. | The SDK uses the configured log level as a threshold: only log events at the specified level and less verbose (more severe) are recorded. For example, setting the level to `Info` captures `Info`, `Warning`, `Error`, and `Assert` events, but not `Debug` or `Verbose`. ## Configure logging at initialization [#configure-logging-at-initialization] You can configure logging behavior by passing a `loggerConfiguration` object to the SDK's `initialize` method. This configuration accepts two separate log levels: * **`logLevel`**: Controls which log events are written to the log file. * **`callbackLogLevel`**: Controls which log events trigger the optional callback function. ```swift title="Configure logging during initialization" try await mobileCredentialHolder.initialize( loggerConfiguration: LoggerConfiguration( // [!code highlight] logLevel: .Info, // [!code highlight] callbackLogLevel: .Warning // [!code highlight] ) // [!code highlight] ) ``` * `logLevel`: Sets the minimum level for writing log entries to the log file. Defaults to `.Off`. * `callbackLogLevel`: Sets the minimum level for invoking the callback closure. Defaults to `.Off`. Refer to the [`LoggerConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/loggerconfiguration) reference documentation for additional details. ```kotlin title="Configure logging during initialization" mobileCredentialHolder.initialize( loggerConfiguration = Logger.LoggerConfiguration( logLevel = Logger.LogLevel.INFO, callbackLogLevel = Logger.LogLevel.WARN ), // ... ) ``` * `logLevel`: Sets the minimum level for writing log entries to the log file and Logcat. Defaults to `Logger.LogLevel.OFF`. * `callbackLogLevel`: Sets the minimum level for invoking the callback function. Defaults to `Logger.LogLevel.OFF`. * `logDir`: Optional. Specifies a local directory to store log files. If not provided, logs won't be stored to file. Refer to the [`Logger.LoggerConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.util/-logger/-logger-configuration/index.html) reference documentation for additional details. ```ts title="Configure logging during initialization" import { LogLevel } from "@mattrglobal/mobile-credential-holder-react-native" const initializeResult = await mobileCredentialHolder.initialize({ loggerConfiguration: { // [!code highlight] logLevel: LogLevel.Info, // [!code highlight] callbackLogLevel: LogLevel.Warn, // [!code highlight] }, // [!code highlight] }) if (initializeResult.isErr()) { const { error } = initializeResult // handle error scenarios return } ``` * `logLevel`: Sets the minimum level for writing log entries to the log file and console. Defaults to `LogLevel.Off`. * `callbackLogLevel`: Sets the minimum level for invoking the callback function. Defaults to `LogLevel.Off`. * `logDir`: Optional. Specifies a directory to store log files. On iOS, a default directory is used. On Android, logs won't be stored to file if not provided. Refer to the [`LoggerConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/LoggerConfiguration.html) reference documentation for additional details. ## Handle log events with a callback [#handle-log-events-with-a-callback] You can register a callback function during initialization to receive log events in real time. This allows your application to process log events as they occur, for example to forward them to a custom logging service, display them in a debug console, or filter specific events for monitoring. The callback is only invoked for log events at or above the `callbackLogLevel` threshold. ```swift title="Register a logging callback" try await mobileCredentialHolder.initialize( loggerConfiguration: LoggerConfiguration( logLevel: .Info, callbackLogLevel: .Warning, callback: { logEvent in // [!code highlight] print("[\(logEvent.level)] \(logEvent.message)") // [!code highlight] } // [!code highlight] ) ) ``` The callback receives a [`LogEvent`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/logevent) object with the following properties: * `level`: The [`LogLevel`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/loglevel) of the event. * `message`: A string describing the log event. ```kotlin title="Register a logging callback" mobileCredentialHolder.initialize( loggerConfiguration = Logger.LoggerConfiguration( logLevel = Logger.LogLevel.INFO, callbackLogLevel = Logger.LogLevel.WARN, callback = { priority, tag, message, throwable -> // [!code highlight] Log.d("HolderSDK", "[$tag] $message") // [!code highlight] } // [!code highlight] ), // ... ) ``` The callback function receives the following parameters: * `priority`: An integer representing the log priority level. * `tag`: An optional string tag identifying the log source. * `message`: A string describing the log event. * `throwable`: An optional `Throwable` associated with the log event (for error-level logs). ```ts title="Register a logging callback" import { LogLevel } from "@mattrglobal/mobile-credential-holder-react-native" const initializeResult = await mobileCredentialHolder.initialize({ loggerConfiguration: { logLevel: LogLevel.Info, callbackLogLevel: LogLevel.Warn, callback: (log) => { // [!code highlight] console.log(`[${log.logLevel}] ${log.tag ?? ""}: ${log.message ?? ""}`) // [!code highlight] }, // [!code highlight] }, }) if (initializeResult.isErr()) { const { error } = initializeResult // handle error scenarios return } ``` The callback receives a log object with the following properties: * `logLevel`: The [`LogLevel`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/enums/LogLevel.html) of the event. * `message`: An optional string describing the log event. * `tag`: An optional string tag identifying the log source. ## Access the log file [#access-the-log-file] The SDK writes log entries to a file that you can access for debugging and diagnostics. The log file contains entries from the previous two calendar days. ```swift title="Get the log file path" let logFilePath = mobileCredentialHolder.getCurrentLogFilePath() // If using an app group (e.g. for app extensions): let logFilePath = mobileCredentialHolder.getCurrentLogFilePath(appGroup: "group.com.yourcompany.app") ``` The [`getCurrentLogFilePath`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/getcurrentlogfilepath\(appgroup:\)) method returns the file path as a string, or `nil` if no log file is available. * `appGroup`: Optional. Specify an application group to retrieve logs generated by the SDK in an app extension. If not provided, the SDK returns the log file from the default location. To access the log contents, read the file at the returned path into `Data`, then decode that data into a `[String]`, where each element represents a single log message. ```kotlin title="Get the log file path" val logFilePath = mobileCredentialHolder.getLog() ``` The [`getLog`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/get-log.html) method returns the full path of the log file (with a `.log` extension), or `null` if no log file is available. The file contains log entries from the previous two calendar days. ```ts title="Get the log file path" const logFilePath = await mobileCredentialHolder.getCurrentLogFilePath() // If using an app group (iOS only): const logFilePath = await mobileCredentialHolder.getCurrentLogFilePath({ appGroup: "group.com.yourcompany.app", }) ``` The [`getCurrentLogFilePath`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/getCurrentLogFilePath.html) method returns a `Promise` resolving to the log file path string, or `null` if no log file is available. * `appGroup`: Optional (iOS only). Specify an application group to retrieve logs generated by the SDK in an app extension. # SDK Tethering URL: /docs/holding/sdk-operations/sdk-tethering Description: Learn how MATTR Holder SDKs are tethered to a MATTR VII tenant through Holder Application configuration, enabling capabilities like Wallet Attestation. SDK Tethering ties each SDK/app instance to a MATTR VII tenant. Tethering establishes a trust relationship between your mobile application and the MATTR VII tenant, enabling the following capabilities: * **Operational insights**: View details about registered and active app instances directly from your tenant. * **Wallet Attestation**: Enable issuers to verify wallet integrity before issuing credentials. See [Wallet Attestation](/docs/holding/credential-claiming-guides/wallet-attestation) for more information. * **Remote management channel**: SDK Tethering establishes a channel that we expect to extend in the future with capabilities such as remote syncing of trusted issuer lists and eventing. SDK Tethering is available from the following SDK versions: * **iOS Holder SDK**: 6.0.0 * **Android Holder SDK**: 7.0.0 * **React Native Holder SDK**: 10.0.0 SDK Tethering is currently **optional**. You enable it by providing a platform configuration when initializing the SDK. If you initialize the SDK without a platform configuration, the SDK skips registration and tethering, and capabilities such as Wallet Attestation are unavailable. We expect to make SDK Tethering **required** in an upcoming release, so we recommend configuring it now to prepare. ## How it works [#how-it-works] The tethering process involves three steps: 1. **Configure a Holder Application on your MATTR VII tenant**: You register your mobile app by creating a Holder Application, identified by the bundle identifier (iOS) or the package fingerprint (Android). 2. **Initialize the SDK with your tenant details**: When you initialize the SDK in your app, you pass the details of the MATTR VII tenant and the Holder Application you configured on it. 3. **Automatic communication**: Once initialized, instances of your app will automatically communicate with the configured MATTR VII tenant and retrieve the required tokens to operate and make requests to the tenant when required. ## Token validity and offline use [#token-validity-and-offline-use] The tokens issued during this process have configurable validity periods controlled by the `maxTimeOfflineInSecs` field on your Holder Application configuration. This means your app can function without internet connectivity to meet different use cases: * **Minimum**: 1 day (86400 seconds) * **Maximum**: 30 days (2592000 seconds) * **Default**: 7 days (604800 seconds) When the license token expires, the SDK must reconnect to the MATTR VII tenant to renew it. ## Configuring SDK Tethering [#configuring-sdk-tethering] ### Configure Holder Applications [#configure-holder-applications] SDK Tethering is optional. To enable it, create a Holder Application on your MATTR VII tenant for each platform target (iOS and Android). This is a one-time setup process that registers your app with the tenant and allows app instances to obtain the necessary tokens for authentication and operation. Make a request of the following structure to create an iOS Holder Application configuration on your MATTR VII tenant: ```http title="Request" POST /v1/holder/applications ``` ```json title="Request body" { "name": "My iOS Holder Application", "clientId": "my-wallet-client", "type": "ios", "bundleId": "com.yourcompany.holderapp", "teamId": "YOUR_APPLE_TEAM_ID", "maxTimeOfflineInSecs": 864000, "appAttest": { "required": true, "environment": "production" } } ``` * `name`: A unique name to identify this Holder Application. * `clientId`: OAuth 2.0 `client_id` value that the holder application uses when requesting client attestations. This value is included as the `sub` claim in attestation JWTs and must match the `client_id` configured by issuers who trust this Holder Application. * `type`: Must be `ios`. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID. * `maxTimeOfflineInSecs`: Maximum number of seconds the SDK can operate offline before requiring a new license token. Must be between 1 day (86400) and 30 days (2592000). Defaults to 7 days (604800). * `appAttest`: App Attest configuration for the iOS holder application: * `required`: When `true`, the app instance must provide a valid App Attest attestation during registration and token renewal. When `false`, the app can fall back to assertion-only authentication. See [Attestation vs Assertion](/docs/holding/sdk-operations/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `environment`: The App Attest environment (`development` or `production`). Apple recommends using `development` for testing and `production` for distribution builds. A successful response returns a `201` status code with the created Holder Application: ```json title="Response" { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", // [!code focus] "name": "My iOS Holder Application", "clientId": "my-wallet-client", "type": "ios", "bundleId": "com.yourcompany.holderapp", "teamId": "YOUR_APPLE_TEAM_ID", "maxTimeOfflineInSecs": 864000, "appAttest": { "required": true, "environment": "production" } } ``` * `id`: A unique identifier for the Holder Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. Make a request of the following structure to create an Android Holder Application configuration on your MATTR VII tenant: ```http title="Request" POST /v1/holder/applications ``` ```json title="Request body" { "name": "My Android Holder Application", "clientId": "my-wallet-client", "type": "android", "packageName": "com.yourcompany.holderapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "maxTimeOfflineInSecs": 864000, "keyAttestation": { "required": true } } ``` * `name`: A unique name to identify this Holder Application. * `clientId`: OAuth 2.0 `client_id` value that the holder application uses when requesting client attestations. This value is included as the `sub` claim in attestation JWTs and must match the `client_id` configured by Issuers who trust this Holder Application. * `type`: Must be `android`. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/holding/android-app-signing) for more information. * `maxTimeOfflineInSecs`: Maximum number of seconds the SDK can operate offline before requiring a new license token. Must be between 1 day (86400) and 30 days (2592000). Defaults to 7 days (604800). * `keyAttestation`: Key Attestation configuration for the Android holder application: * `required`: When `true`, the app instance must provide a valid Key Attestation during registration and token renewal. When `false`, the app can register and renew tokens using just an authentication assertion. See [Attestation vs Assertion](/docs/holding/sdk-operations/sdk-tethering#attestation-vs-assertion-fall-back) for more details. A successful response returns a `201` status code with the created Holder Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", // [!code focus] "name": "My Android Holder Application", "clientId": "my-wallet-client", "type": "android", "packageName": "com.yourcompany.holderapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "maxTimeOfflineInSecs": 864000, "keyAttestation": { "required": true } } ``` * `id`: A unique identifier for the Holder Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. For React Native applications, you must create **both** an iOS and an Android Holder Application on your MATTR VII tenant, and then conditionally pass the correct configuration based on the platform OS at runtime. **Step 1: Create the iOS Holder Application** Follow the instructions in the **iOS** tab to create a Holder Application configuration for iOS. **Step 2: Create the Android Holder Application** Follow the instructions in the **Android** tab to create a Holder Application configuration for Android. **Step 3: Pass the correct configuration based on Platform OS** When initializing the SDK, use `Platform.OS` to conditionally provide the matching Holder Application configuration: ```typescript title="Example" import { Platform } from "react-native"; const holderApplicationId = Platform.OS === "ios" ? "YOUR_IOS_HOLDER_APPLICATION_ID" : "YOUR_ANDROID_HOLDER_APPLICATION_ID"; ``` * `YOUR_IOS_HOLDER_APPLICATION_ID` : The `id` returned when you created the iOS Holder Application. * `YOUR_ANDROID_HOLDER_APPLICATION_ID` : The `id` returned when you created the Android Holder Application. ### Initialize the SDK with platform configuration [#initialize-the-sdk-with-platform-configuration] If you are using SDK Tethering, update your SDK initialization to include the platform configuration once your Holder Applications are created. This enables your app to connect to the correct MATTR VII tenant and Holder Application. If you are not using tethering, you can initialize the SDK without a `platformConfiguration`. Initialize the SDK with your platform configuration: ```swift title="Initialization" let platformConfig = PlatformConfiguration( tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, applicationId: "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) try await MobileCredentialHolder.shared.initialize( platformConfiguration: platformConfig ) ``` * `tenantHost`: The URL of your MATTR VII tenant. This must be the tenant where your iOS Holder Application is configured. * `applicationId`: The `id` of your configured iOS Holder Application. Initialize the SDK with your platform configuration: ```kotlin title="Initialization" val platformConfig = PlatformConfiguration( tenantHost = URL("https://your-tenant.vii.mattr.global"), applicationId = "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) MobileCredentialHolder.initialize(context, platformConfiguration = platformConfig) ``` * `tenantHost`: The URL of your MATTR VII tenant where your Android Holder Application is configured. * `applicationId`: The `id` of your configured Android Holder Application. Since React Native bridges both iOS and Android, and each platform has its own Holder Application registered on your MATTR VII tenant (see [Create a Holder Application](/docs/holding/sdk-getting-started#create-a-holder-application)), your initialization code must pass the correct platform-specific `applicationId` at runtime. Use `Platform.OS` to select the appropriate value: ```typescript title="Initialization" import { initialize } from "@mattrglobal/mobile-credential-holder-react-native"; import { Platform } from "react-native"; const applicationId = Platform.OS === "android" ? "your-android-holder-application-id" : "your-ios-holder-application-id"; await initialize({ platformConfiguration: { tenantHost: "https://your-tenant.vii.mattr.global", applicationId, }, }); ``` Replace the placeholder values with the `id` returned when you created each Holder Application. In practice, you would typically store these values in a configuration file or environment variables. Once your Holder Application configurations are created, your application will be able to use the SDK and interact with the MATTR VII platform (for example, to obtain attestation tokens). ## Managing application instances [#managing-application-instances] Once your Holder Application is configured and the SDK is initialized, each device that launches your app registers as a new application instance on your tethered MATTR VII tenant. You can view and manage these instances via the MATTR VII API. ### Retrieve all registered instances [#retrieve-all-registered-instances] To view all registered instances for a Holder Application and track usage: ```http title="Request" GET /v1/holder/applications/{applicationId}/instances ``` * `applicationId` : The `id` of the Holder Application you want to inspect. The response includes a paginated list of all registered instances: ```json title="Response" { "data": [ { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "appAttestationType": "app_attestation", "registeredAt": "2023-10-05T14:48:00.000Z", "licenseExpiresAt": "2024-10-05T14:48:00.000Z", "lastAttestedAt": "2023-12-01T10:30:00.000Z", "externalReferenceId": "external-ref-12345", "deviceDetails": { "deviceModel": "iPhone 12", "deviceMake": "Apple", "osVersion": "iOS 14.4" }, "sdkDetails": { "sdkVersion": "1.2.3" } } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` Each instance includes: * `id` : Unique identifier for the registered instance. * `appAttestationType` : The type of attestation used during registration (`none`, `app_attestation`, or `key_attestation`). * `registeredAt` : When the instance was first registered. * `licenseExpiresAt` : When the instance's license expires (the Holder SDK will automatically handle license renewal). * `lastAttestedAt` : When the instance was last attested. * `deviceDetails` : Information about the device (model, make, OS version). * `sdkDetails` : Information about the SDK version used by the instance. This is useful for tracking how many devices are actively using your application and monitoring usage quotas. ### Delete a specific instance [#delete-a-specific-instance] To remove a specific registered instance: ```http title="Request" DELETE /v1/holder/applications/{applicationId}/instances/{instanceId} ``` * `applicationId` : The `id` of the Holder Application. * `instanceId` : The `id` of the specific instance to delete. Once deleted, the instance can no longer interact with the platform or receive tokens, and any existing tokens are revoked. Deleting instances is primarily useful during **testing** when you have a limited number of devices and need to re-register a fresh instance (for example, to test the initial registration flow again). In production, there is nothing preventing the application from requesting another token on the next launch, which would create a new instance — so deleting instances is not an effective way to block a device. ## Attestation vs Assertion fall-back [#attestation-vs-assertion-fall-back] When configuring a Holder Application, you control whether your MATTR VII tenant requires **attestation** (hardware-backed proof of app integrity) or also accepts a lighter-weight **assertion** (a cryptographic signature proving key possession) during instance registration and token renewal. Each platform has an attestation configuration with a `required` boolean: * When `required` is `true`, the app instance must provide a valid attestation during registration and token renewal. * When `required` is `false`, your tenant also accepts an assertion when an attestation is not available. The SDK handles this automatically. It always attempts to provide an attestation, and falls back to an assertion if it cannot generate one (for example, when the platform attestation service is temporarily unavailable). Your tenant then accepts or rejects the request based on the `required` setting. Your application does not need to manage attestation or assertion details directly. ### When to use each setting [#when-to-use-each-setting] | Scenario | Recommended setting | | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | **Production apps in distribution** | `required: true` — Provides the strongest integrity guarantees by verifying the app and device through OS-level attestation. | | **Development and testing** | `required: false` — Useful when running on simulators or devices where attestation services are unavailable. | | **Broad device compatibility** | `required: false` — Some older devices may not support hardware attestation. The assertion fall-back ensures these devices can still register. | Setting attestation to `required: false` reduces the security guarantees of the tethering process. Only use this setting when you have a specific need, such as supporting older devices or during development. ### Certificate expiry and required Key Attestation (Android) [#certificate-expiry-and-required-key-attestation-android] This behavior is specific to **Android Key Attestation**. It does not apply to iOS App Attest. When Key Attestation is set to required, an attestation must be present, parseable, and trusted (a trusted root with each certificate in the chain signed by the one above it) for registration to succeed. One thing to be aware of: our validation does not check whether the certificates in the attestation chain have expired. Some Android devices, including relatively recent models, generate attestations where part or all of the certificate chain is already past its validity period. To avoid blocking these devices, an otherwise valid attestation with expired certificates is still treated as a successful attestation, even when Key Attestation is set to required. In practice this means a device presenting an expired (but otherwise valid) attestation chain will register successfully and be marked as attested. This behavior may be tightened in a future release, so we recommend not relying on certificate expiry as part of your own trust decision. # API Reference URL: /docs/issuance/authorization-code/api-reference ## Create Credential Offer [#create-credential-offer] ## Request authorization for access to resources [#request-authorization-for-access-to-resources] ## Exchange authorization code for access token [#exchange-authorization-code-for-access-token] ## Issue a verifiable credential [#issue-a-verifiable-credential] ## Retrieve issuer metadata [#retrieve-issuer-metadata] # OID4VCI Authorization Code flow journey pattern URL: /docs/issuance/authorization-code/journey-pattern This journey pattern is used to issue credentials of different formats to a holder via the OID4VCI [Authorization Code](/docs/issuance/authorization-code/overview) flow. ## Overview [#overview] * **Issuance channel**: Remote, Unsupervised * **Device/s**: On-device / Cross-device / in-person * **Formats**: mDocs, CWT, Semantic CWT * **Information assurance level**: High * **Identity assurance level**: High (when identity assurance checks are included) ## Journey flow [#journey-flow] OID4VCI Authorization Code journey pattern ## Architecture [#architecture] OID4VCI architecture ### Scanning the QR code [#scanning-the-qr-code] The QR code that is used to initiate the issuance workflow is created by the Issuer, but the Holder selects when to scan it using their digital wallet (1) which triggers the issuance workflow. This QR code can be sent to the holder via any existing communication channels, including digital and paper based channels. ### Credential offer [#credential-offer] Once the QR code is scanned it will result in the wallet displaying the credential offer that was created by the Issuer using MATTR VII issuance capabilities. The offer details the credential formats that will be issued in this workflow, and what details would each credential include. Digital Trust Service capabilities (9) enable creating and maintaining policies that define what Issuers can be trusted and what credential types they are allowed to issue. This introduces an additional level of trust to interactions within the trust network, making it easier for Samantha to decide whether or not she wishes to claim a credential from this Issuer. ### Obtaining a binding attribute [#obtaining-a-binding-attribute] The OpenID Credential Provisioning (2) component commences the credential issuance flow by obtaining a unique binding attribute from the requesting device/wallet. This happens when the user accepts the credential offer (step 3 in the pattern above). The binding attribute is carried through the proceeding steps to bind the intended credential holder and the data. ### Authentication [#authentication] The OpenID Connect Provider (IdP) is a component managed by the Issuer to authenticate users against the data they hold about them. The Issuer’s IdP (3) facilitates the authentication flow (step 4 in the pattern above) which may include multiple steps that test different factors such as: * Basic username/password authentication. * Proving device ownership through acknowledging a unique request sent directly to the device. * Providing genuine presence assurance (liveness check) that the correct user is in possession of the device being used to facilitate the journey. The issuer can configure this authentication flow requests to include login hint parameters (for example pre populating the user e-mail address) to create more seamless user experiences. ### Interaction hook [#interaction-hook] Following successful authentication, the user can be redirected to a custom component (4) hosted or controlled by the Issuer (step 5 in the pattern above). This custom component can initiate additional steps for the user to perform as part of issuing the credential. This can be used to embed enrolment within the issuance journey. We refer to these custom components as *Interaction hooks*. ### Claims source [#claims-source] As part of gathering authenticated claims that are included in the issued credential, the Issuer may want to retrieve additional information about the user from external data store/s (5). This optional step (step 6 in the pattern above) enables issuing credentials that are more rich in information and thus provide greater value. It is achieved by configuring an external data store, which we refer to as a *Claims source*. ### Credential generation [#credential-generation] The information then gets passed back through the OpenID Credential Provisioning component (2) to map against an established vocabulary, and express the intended context around each piece of information it holds. The mapped data is then passed to the Credential Generation component (6) which formats, binds and signs the data into a credential that is ready to be sent to the requesting wallet/device (step 7 in the pattern above). The credential can be issued in multiple-formats. Additional features may be enabled to support capabilities such as credential revocation or allowing the holder to respond to selective-disclosure verification requests. ### Credential management [#credential-management] Digital wallets (1) can be used to manage the acceptance and secure storage of the credential on the Holder’s device upon completion of the credential issuance flow (step 8 in the pattern above). This can be achieved by wallets built with our MATTR Pi Wallet SDK or branded MATTR GO Hold applications. ### Webhooks [#webhooks] At the completion of the issuance flow, MATTR VII will trigger any configured Webhook events to configured recipients (7). These events (step 9 in the pattern above) offer additional information about the credential issuance (such as the wallet DID) back to the issuer for them to utilize or store. This enables integrating issuance workflows into existing business processes, or creating new ones based on this capability. # OID4VCI Authorization Code workflow URL: /docs/issuance/authorization-code/overview ## Overview [#overview] The Authorization Code flow is an interactive, user-centric flow where the credential recipient (usually a wallet) redirects the user to authenticate with the issuer (e.g., a government or organization). After consent and authentication, the wallet exchanges an authorization code for a credential. This flow is ideal for scenarios where the user needs to provide explicit consent and may require additional authentication steps. It is also useful when the issuer needs to gather additional information from the user or perform additional checks before issuing the credential. ## Workflow [#workflow] The following diagram depicts the OID4VCI Authorization Code flow: ### Creating a Credential offer [#creating-a-credential-offer] The workflow begins when an issuer creates what is referred to as a [credential offer](/docs/issuance/credential-offer/overview), used to share important information with the holder’s digital wallet. This includes the credential’s issuer, what it includes and how to claim it. The OID4VCI specification defines a method for a digital wallet to discover this information in a standardized and interoperable way. ### Sharing a Credential offer [#sharing-a-credential-offer] Credential offers are created by making a request to a MATTR VII endpoint that returns an offer URI. This URI is shared with the intended holder as a QR code, deep-link or wallet push notification. The offer only includes metadata required to initiate the issuance process, and the actual credential is only created and signed later in the workflow. Refer to [Claiming credential offers](/docs/issuance/credential-offer/overview#claiming-credential-offers) for more details on how to adjust the offer URI to use specific schemes and the resulting user experience. ### Accepting a Credential offer [#accepting-a-credential-offer] After receiving the credential offer URI, the wallet uses it to retrieve required metadata from the `.well-known` endpoints: ```http title="Issuer Metadata Endpoint" https://{your_tenant_url}/.well-known/openid-credential-issuer ``` ```http title="Authorization Server Metadata Endpoint" https://{your_tenant_url}/.well-known/oauth-authorization-server ``` As required by the OID4VCI specification, these endpoints must be publicly accessible. They provide the information necessary to initiate the issuance process, including the issuer’s details, the types of credentials available, and the relevant endpoints and supported parameters used throughout the workflow. The wallet then presents relevant information from the credential offer to the holder and requests their consent to proceed. Upon receiving consent, the wallet initiates the Authorization Code flow, redirecting the holder to the issuer’s authorization endpoint to authenticate and authorize the credential issuance. **Reusable offers**: Authorization Code flow offers can be claimed multiple times by different users. Since each user authenticates independently during the credential issuance workflow, the same offer URI can be used to issue credentials to multiple holders. Each holder will receive a credential containing their own user-specific claims, gathered during their individual issuance session. This makes Authorization Code offers suitable for scenarios like: * QR codes displayed publicly (e.g., at an event registration desk) * Links shared with multiple recipients (e.g., an email to a group) * Self-service credential issuance portals ### Authentication [#authentication] When the holder consents they are redirected to a configured [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview), used to authenticate and identify the holder. This provider can also be used as an identity provider to retrieve claims about the authenticated user. Once retrieved, these claims can be used in the issued credential. After the holder completes authentication, they are redirected from the Authentication provider back to the issuance workflow. Inline with the [OID4VCI specification](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-token-endpoint), this step includes receiving a `code` from the Authentication provider upon successful authentication and exchanging it with a Token that is used later in the workflow to issue the credential. ### Custom components (*optional*) [#custom-components-optional] This is where issuers can *optionally* add their own business logic to the workflow by configuring an [Interaction hook](/docs/issuance/authorization-code/interaction-hook/overview). This is *optional* step enables issuers to plug in any custom experiences to interact with the holder after they have authenticated but before the credential is issued. For example, this can be used to gather additional information from the user or to add higher identity assurance components such as a liveness or biometrics checks. ### Querying an external database (*optional*) [#querying-an-external-database-optional] Following the interaction hook, issuers can configure another *optional* step to retrieve additional user claims from an existing databases and use it in the issued credential. This integration is referred to as a [Claims source](/docs/issuance/claims-source/overview). Claims sources can query the configured endpoints with any of the user claims retrieved from the Authentication provider and/or Interaction hook. This enables the issuer to create flexible and efficient queries to retrieve the exact user claims required to issue a specific credential. ### Formatting and Signing the credential [#formatting-and-signing-the-credential] Now that all the data is available, MATTR VII will format it into a digital credential and cryptographically sign it. This is achieved using a [Credential configuration](/docs/issuance/credential-configuration/overview) that is defined as part of the credential offer. This is a template that defines how should the issued credential be constructed. It details where the credential claims are mapped from, what the credential should look like in the wallet, when and if it should expire and whether it is revocable. ### Delivering the signed credential [#delivering-the-signed-credential] Finally, the signed credential is delivered directly to the holder’s digital wallet and can be presented to verifying parties upon request. ## Configuration [#configuration] The OID4VCI workflow makes use of different components that can be configured either using direct API requests to a MATTR VII tenant or via the [MATTR Portal](/docs/platform-management/portal). * [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) (*required*): An authentication provider is used to store and manage user accounts on behalf of an organization or service provider. The provider will be the first screen users see when trying to claim a credential as part of an OID4VCI workflow. Usually this is a login page to verify user credentials, but it could be any custom implementation as long as it follows the OpenID Connect standard. * [Interaction hook](/docs/issuance/authorization-code/interaction-hook/overview) (*optional*): If you would like to integrate with any existing components beyond a login page (e.g. MFA, biometrics checks, consent screens), you can set up an interaction hook to redirect users. You can configure the interaction hook to pass back additional user claims or modify existing ones. The interaction hook enables adding additional steps to the credential-claiming journey by bouncing users to your own web service. * [Claims source](/docs/issuance/claims-source/overview) (*optional*): Usually authentication providers do not store all the information about a user and only keep attributes like email, names or other short identifiers. When issuing credentials, you will likely need more user information and to accommodate that, the workflow enables retrieving data from a custom claim source via a single API call. If you have additional user information stored in a separate database or service, add a claims source to fetch claims directly from a compatible standalone system and use these claims when issuing credentials. * [Credential configuration](/docs/issuance/credential-configuration/overview) (*required*): Add your credential types, branding, claims, and other metadata. You can also mix and match where claims for the issued credentials come from - an authentication provider, an interaction hook, or a claims source. # OID4VCI Authorization Code flow quickstart guide URL: /docs/issuance/authorization-code/quickstart This quickstart is for evaluating MATTR’s OID4VCI Authorization Code flow issuance capabilities. In about 10-15 minutes you will configure an OID4VCI Authorization Code flow in the MATTR Portal, generate a credential offer, and claim an mDoc into the GO Hold example app. **Estimated time: 10-15 minutes.** Use this guide as a fast evaluation path to see the flow working end-to-end. For detailed information and API examples, explore the [tutorial](/docs/issuance/authorization-code/tutorial) and reference documentation. ## User experience [#user-experience] In this quickstart you will perform this exact flow yourself using the MATTR Portal, a mock authentication provider, and the GO Hold example app: OID4VCI Tutorial Workflow 1. User scans a QR code from an issuer. 2. The wallet displays what credential is being offered. 3. The user agrees to claim the offered credential. 4. The user is redirected to complete authentication. 5. Upon successful authentication, the credential is issued to the wallet. ## Prerequisites [#prerequisites] * MATTR VII tenant access via the [MATTR Portal](https://portal.mattr.global/). Apply for access [here](/docs/resources/get-started). * Install the **MATTR GO Hold example app** for [iOS](https://apps.apple.com/app/mattr-wallet/id1518660243) or [Android](https://play.google.com/store/apps/details?id=global.mattr.wallet). ## Steps [#steps] ### Configure mock Authentication provider (3 minutes) [#configure-mock-authentication-provider-3-minutes] This quickstart uses a MATTR-hosted mock authentication provider to keep setup simple. In production you will configure your own IdP (for example Auth0) and real users. See the [full tutorial](/docs/issuance/authorization-code/tutorial) for a detailed Auth0 example. 1. Log into the [MATTR Portal](https://portal.mattr.global/). 2. Switch to your tenant if you have access to multiple tenants, or [create a new tenant](/docs/platform-management/portal#creating-a-tenant) if needed. 3. Expand **Credential Issuance**. 4. Select **Authentication provider**. 5. Enter `https://learn.au.auth.mattrlabs.com/` in the **Base URL** field. 6. Enter any string in the **Client ID** and **Client Secret** fields (these are ignored by the mock Authentication provider). 7. Select **Create**. ### Create issuer certificate (2 minutes) [#create-issuer-certificate-2-minutes] This allows your tenant to act as an issuing authority for mDocs in this demo. This step is only required if you haven't already set up an issuer certificate for your tenant. If you already have an active IACA, skip to the next step. 1. Expand **Platform Management**. 2. Select **Certificates**. 3. Select **Create new**. 4. Select **IACA - Issuing Authority Certificate Authority** as the type. 5. Select **MATTR managed** as the management method. 6. Select **Create**. 7. Set **Status** to **Active**. 8. Select **Update** to activate the certificate. ### Create mDoc credential configuration (3 minutes) [#create-mdoc-credential-configuration-3-minutes] In this quickstart you’ll use a simple, pre-defined credential configuration with default claim values so you can issue a credential without integrating any external data sources: 1. Expand **Credential Issuance**. 2. Select **mDocs**. 3. Select **Create new**. 4. Enter a **Name** (e.g., "My First Credential"). 5. Enter a **Description** (e.g., "Claimed via Authorization Code flow"). 6. Enter a unique **Credential type** (e.g., `com.example.authcodecredential`). 7. Paste the following JSON into **Claim mappings**: ```json title="Claim mappings object" { "com.example.personaldetails.1": { "name": { "defaultValue": "Emma Tasma", "type": "string" }, "email": { "defaultValue": "emma.tasma@example.com", "type": "string" } } } ``` 8. Enter "1" in the **Months** field under **Validity for**. 9. Select **Create**. ### Generate a credential offer (2 minutes) [#generate-a-credential-offer-2-minutes] This creates the OID4VCI offer that wallets can use to start the Authorization Code flow. 1. Expand **Credential Issuance**. 2. Select **Credential offer**. 3. Select **Authorization code flow** as the workflow. 4. Select the **Select** button. 5. Check the checkbox next to your credential configuration. 6. Select **Apply**. 7. Select **Generate**. 8. Download and save the QR code (or just leave it on the screen for scanning in the next step). ### Claim the credential (2 minutes) [#claim-the-credential-2-minutes] Now use the GO Hold example wallet to experience the end-to-end flow from QR scan to credential in the wallet: 1. Open the [**GO Hold example app**](/docs/holding/go-hold/getting-started). 2. Select **Share** on the home screen. 3. Select **Respond or Collect** (You may need to allow the app to access your camera). 4. Scan the QR code you generated in the previous step. 5. Review the credential offer and select **Proceed**. 6. When prompted to open the authentication page, select **Continue**. 7. Select **Sign in** to complete the authentication flow (you do not need to change any of the login details). 8. You will be redirected back to the GO Hold app where you can see the issued credential. Behind the scenes, MATTR handled the OID4VCI Authorization Code flow, including redirecting to the mock authentication provider, validating the user, and issuing the mDoc to your GO Hold example app. Congratulations! You've successfully configured an OID4VCI Authorization Code flow and issued an mDoc to a digital wallet using only MATTR Portal configuration and the GO Hold example app. ## Next steps [#next-steps] * For your evaluation: * Note how long this quickstart took and any friction you encountered. * Review the credential configuration and consider how it would map to your real data. * Explore the [OID4VCI Authorization Code flow tutorial](/docs/issuance/authorization-code/tutorial) for detailed instructions and explanations. * Try the [OID4VCI Pre-authorized Code flow](/docs/issuance/pre-authorized-code/quickstart) for a different issuance workflow. # OID4VCI Authorization Code flow tutorial URL: /docs/issuance/authorization-code/tutorial ## Introduction [#introduction] The [OID4VCI specification](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) is an open standard developed by the OpenID Foundation. It leverages the OpenID protocol to support verifiable credentials issuance and management. In this tutorial we will configure an [OID4VCI Authorization Code flow](/docs/issuance/authorization-code/overview) and use it to issue an [mDoc](/docs/concepts/mdocs) to the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started), showing each step both through the MATTR Portal and via the equivalent API calls. ## User experience [#user-experience] OID4VCI Tutorial Workflow This is the user experience you will build in this tutorial: 1. User launches their [GO Hold example app](/docs/holding/go-hold/getting-started) and scans a QR code received from an issuer. 2. The wallet displays what credential is being offered to the user and by what issuer. 3. The user agrees to claiming the offered credentials. 4. The user is redirected to complete authentication via Auth0. 5. Upon successful authentication, the credential is issued to the user's GO Hold example app. They can now view the credential and present it for verification. ## Prerequisites [#prerequisites] * Complete the [sign up form](/docs/resources/get-started) to get trial access to MATTR VII and the MATTR Portal, and then [Create a tenant](/docs/platform-management/portal#creating-a-tenant). * Install the **MATTR GO Hold example app** by following the [getting started guide](/docs/holding/go-hold/getting-started). * Sign up with [Auth0](https://auth0.com/signup) which we will use to authenticate the holder as part of the issuance workflow. We recommend using the MATTR VII [Postman collection](/docs/api-reference#postman-collection) in this tutorial. While this isn't an explicit prerequisite it can really speed things up. ## Tutorial overview [#tutorial-overview] To build this user experience, the current tutorial comprises the following steps: 1. [Configure an Auth0 application](#configure-an-auth0-application): We will use this application to authenticate the user as part of the [OID4VCI Authorization Code flow](/docs/issuance/authorization-code/overview). 2. [Obtain a MATTR VII access token](#obtain-a-mattr-vii-access-token): You need this to access protected endpoints on your MATTR VII tenant. 3. [Configure an Authentication provider](#configure-a-mattr-vii-authentication-provider): Required to authenticate users before you can issue them credentials. 4. [Create Issuer certificates](#create-issuer-certificates): Required to sign mDocs. 5. [Create an mDocs credentials configuration](#create-a-mattr-vii-mdocs-credentials-configuration): Controls the content and branding of issued Credentials. 6. [Create and share a Credential offer](#create-a-credential-offer): Used by digital wallets to trigger the issuance workflow. 7. [Claim the credential as the holder](#use-the-mattr-go-example-app-to-claim-the-credential): Use your GO Hold example app to claim the offered Credential. ## Tutorial steps [#tutorial-steps] ### Configure an Auth0 application [#configure-an-auth0-application] Let's start by configuring an Auth0 application. This application will be used to authenticate the user as part of the [OID4VCI Authorization Code flow](/docs/issuance/authorization-code/overview). ##### Create a new Auth0 application [#create-a-new-auth0-application] 1. Log into [Auth0](https://auth0.com/). 2. Skip the Auth0 onboarding tutorials and go straight to your **Dashboard**. 3. Select **Create Application**. 4. Insert a *Name* for your application. 5. Select **Regular Web Application**. 6. Select **Create**. 7. Select **Skip Integration**. Your application is created and you will be redirected to the **Settings** tab under the **Applications** section. 8. Record your application `Domain`, `Client ID` and `Client Secret`. 9. Add a simple **Description**. 10. Scroll down to the *Application URIs* area, locate the *Allowed Callback URLs* textbox and insert the following URL: `https://{your_tenant_url}/core/v1/oauth/authentication/callback` (make sure you replace `{your_tenant_url}` with the `tenant_url` provided with your MATTR VII [tenant details](/docs/platform-management/portal#getting-started)). 11. Select the **Connections** tab. 12. Enable *Username-Password-Authentication* under **Database**. 13. Disable everything under **Social**. ##### Create a User [#create-a-user] 1. Select the **User Management** menu on the left hand side navigation panel and select **Users**. 2. Select the **Create User** button. 3. Add an *Email*. This must be different to the one you use to sign up to your Auth0 account. 4. Add a *password*. 5. Select **Username-Password-Authentication** from the *Connection* dropdown list. 6. Select **Create**. You will be redirected to the new user’s *Details* tab. 7. Select **Edit** under *Name* and replace the value (Auth0 uses the email by default) with the full user name. Auth0 holds and stores information against user objects, **including PII**. Before you add any additional data, make sure you understand [Auth0's policies](https://auth0.com/docs/users/normalized/auth0/store-user-data) around storing this information. ### Configure a MATTR VII Authentication provider [#configure-a-mattr-vii-authentication-provider] Next you need to configure your Auth0 [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) in your MATTR VII tenant. This is required so that MATTR VII can use the Auth0 application to authenticate users before issuing them credentials. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Authentication provider**. 3. Insert your Auth0 application `Domain` in the *Base URL* field. Make sure you prefix it with `https://`. 4. Insert your Auth0 application `Client ID` in the *Client ID* field. 5. Insert your Auth0 application `Client Secret` in the *Client secret* field. 6. Select **Create**. **Step 1: Obtain a MATTR VII access token** All of the MATTR VII endpoints you will use in this tutorial are protected, so you will first need to use your [tenant details](/docs/platform-management/portal#getting-started) to make a request of the following structure and obtain an access token: ```http title="Request" POST https://{auth_server}/oauth/token ``` * `auth_server` : Replace with the `auth_url` value from your tenant details. ```json title="Request body" { "client_id": "F5qae****************************", "client_secret": "Wzc8J**********************************************************", "audience": "learn.vii.au01.mattr.global", "grant_type": "client_credentials" } ``` * Replace `client_id`, `client_secret` and `audience` with your own tenant details. * Keep `grant_type` as `client_credentials`. *Response* ```json title="Response body" { "access_token": "eyJhb********************************************************************", "expires_in": 14400, "token_type": "Bearer" } ``` Use the returned `access_token` value as a bearer token for all requests to your MATTR VII tenant in the next steps of this tutorial. You will need to obtain a new access token whenever it expires. We recommend using our [Postman collection](/docs/api-reference#postman-collection) to interact with your MATTR VII tenant. This collection includes a pre-request script to obtain an access token when it is missing or expired, as well as pre-configured templates for all available requests. **Step 2: Configure an Authentication provider** Now that you have an access token, you can make a request to create an [Authentication provider configuration](/docs/issuance/authorization-code/authentication-provider/overview) based on the details of your Auth0 application. This will be used to authenticate users as part of the [OID4VCI Authorization Code flow](/docs/issuance/authorization-code/overview). Make the following API request to your MATTR VII tenant: ```http title="Request" POST /v1/users/authentication-providers ``` ```json title="Request body" { "url": "https://example.us.auth0.com/", "scope": ["openid", "profile", "email"], "clientId": "zH1IGGkRo6q0ofwiNJnlY0bg9fchw3s0", "clientSecret": "***********************************************************LFRrZ", "tokenEndpointAuthMethod": "client_secret_post", "staticRequestParameters": { "prompt": "login", "maxAge": 10000 } } ``` * `url` : Replace with your Auth0 application `Domain`. Make sure you prefix it with `https`. * `scope` : This determines what claims are returned by the authentication provider following successful authentication. Refer to [Auth0 OIDC scopes](https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes) for more information. * `clientId` : Replace with your Auth0 application `Client ID`. * `clientSecret`: Replace with your Auth0 application `Client Secret`. ### Create issuer certificates [#create-issuer-certificates] In this tutorial you are going to issue an [mDoc](/docs/concepts/mdocs), so you need to have valid [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca). 1. Log into the [MATTR Portal](https://portal.mattr.global/). 2. In the navigation panel on the left-hand side, expand the **Platform Management** menu. 3. Select **Certificates**. 4. Select the **Create new** button. 5. Use the *Type* radio button to select **IACA - Issuing Authority Certificate Authority**. 6. Use the *Management method* radio button to select **MATTR managed**. 7. Use the *Country* dropdown list to select an issuing country. 8. Select the **Create** button to create the IACA certificate.\ The IACA is created as *inactive* by default. 9. Use the *Status* radio button to select **Active**. 10. Select the **Update** button to activate the IACA certificate. 1. Make the following request to your MATTR VII tenant to create an [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca): ```http filename:"Request" POST /v2/credentials/mobile/iacas ``` The response will include an `id` element which you will use in the next step. 2. Make the following request to activate the IACA you just created: ```http filename:"Request" PUT /v2/credentials/mobile/iacas/{iacaId} ``` * `iacaId`: Replace with the `id` element returned in the response when you created the IACA in the previous steps. ```http filename:"Request body" { "active": true } ``` Your IACA is now active and can be used to sign mDocs. ### Create a MATTR VII mDocs credential configuration [#create-a-mattr-vii-mdocs-credential-configuration] Now that you have valid certificates in place, the next component you need is a [Credential configuration](/docs/issuance/credential-configuration/overview) to define the structure and branding of the issued credential. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **mDocs**. 3. Select the **Create new** button. 4. In the *Name* text box, enter a clear and descriptive title that will appear on the credential in the wallet, for example "My First Credential". 5. In the *Description* text box, enter a clear and descriptive description that will appear on the credential in the wallet, for example "Use For High Assurance Interactions". 6. In the *Credential type* text box, enter a unique identifier for the credential type, for example `com.example.myfirstcredential`. 7. Copy and paste the following JSON into the *Claim mappings* text box: ```json title="Claim mappings" { "com.example.personaldetails.1": { "name": { "mapFrom": "claims.name", "type": "string" }, "email": { "mapFrom": "claims.email", "type": "string" } } } ``` 8. Use the *Include status* dropdown list to select **Enable**. This will enable support for changing the revocation status of the issued credentials. 9. Enter "1" in the *Months* text box in the *Validity for* panel to set the credential expiration period. 10. Select the **Create** button to create the credential configuration. Make the following request to create a simple [mDoc credentials configuration](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration) that includes the holder's name and e-mail: ```http title="Request" POST /v2/credentials/mobile/configurations ``` ```json title="Request body" { "type": "com.example.myfirstcredential", "expiresIn": { "months": 1 }, "claimMappings": { "com.example.personaldetails.1": { "name": { "mapFrom": "claims.name", "type": "string" }, "email": { "mapFrom": "claims.email", "type": "string" } } }, "branding": { "name": "My First Credential", "description": "For High Assurance Interactions", "backgroundColor": "#2d46d8" }, "includeStatus": true } ``` The response will include an `id` element. You will use it to create a Credential offer in the next step. ### Create a Credential offer [#create-a-credential-offer] You now have all the pieces in place and can wrap them all together to generate a [Credential offer](/docs/issuance/credential-offer/overview) and share it with the intended holder so that they know what credentials are being offered and by whom. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Credential offer**. 3. Use the *Workflow* radio button to select **Authorization code flow**. 4. Select the **Select** button. 5. Check the checkbox next to the credential configuration you created in the previous step. 6. Select the **Apply** button. 7. Select the **Generate** button. 8. Download the displayed QR code and save it somewhere safe.\ This QR code will be used by the holder to claim the credential. Make the following request to [generate a new Credential offer](/docs/issuance/authorization-code/api-reference#create-credential-offer): ```http title="Request" POST /v1/openid/offers ``` ```json title="Request body" { "credentials": ["945214ad-3635-4aff-b51d-61d69a3c8eee"] } ``` * `credentials`: Populate the array with the `id` element returned in the response when you created an mDocs credentials configuration in the previous step. The response will include a `uri` element which can be used by a digital wallet to trigger the OID4VCI workflow. Use one of the following tools to convert the `uri` value to a QR code (make sure you use the `Plain text` option where available): * [https://www.the-qrcode-generator.com/](https://www.the-qrcode-generator.com/) * [http://goqr.me/api/](http://goqr.me/api/) * [https://www.qr-code-generator.com/](https://www.qr-code-generator.com/) MATTR is not affiliated with any of these service providers and cannot vouch for their offerings. Save the generated QR code on your computer. ### Claim the credential [#claim-the-credential] 1. Open the GO hold example app. 2. Select **Scan**. 3. Scan the QR code generated in the previous step. 4. Review the credential offer and select **Accept**. 5. Follow the issuance workflow instructions to claim the credential. Congratulations, you just configured an end-to-end [OID4VCI Authorization Code flow](/docs/issuance/authorization-code/overview) to issue an mDoc into a digital wallet!!! ## What's next? [#whats-next] In this tutorial we have configured a basic [OID4VCI Authorization Code flow](/docs/issuance/authorization-code/overview). However, you can use MATTR VII to create more complex and rich issuance experience. Check out more resources on MATTR Learn that will enable you to: * Experiment with a different issuance flow by completing the [OID4VCI Pre-authorized Code flow](/docs/issuance/pre-authorized-code/tutorial) tutorial. * Configure a [Claims source](/docs/issuance/claims-source/tutorial) to retrieve data from compatible data sources and use it in the issued credential. * Configure an [Interaction hook](/docs/issuance/authorization-code/interaction-hook/tutorial) to redirect the user to custom components as part of the issuance workflow. * Apply branding to issued credentials as part of creating a [Credential configuration](/docs/issuance/credential-configuration/overview). # How to create issuer certificates URL: /docs/issuance/certificates/guide ## Overview [#overview] An [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) (Issuing Authority Certificate Authority) is a X.509 based certificate used to identify an mDoc issuer and verify the mDocs they issue. MATTR VII supports both managed and unmanaged issuer certificates, allowing issuers to choose how they want to manage their certificate infrastructure: * **Managed IACAs** : MATTR VII automatically creates, stores, and manages the lifecycle of the IACA, including the private key, as well as signing and managing any Document Signer Certificates (DSCs) that are required to sign mDocs. * **Unmanaged (external) IACAs** : The customer creates and manages their own IACA, including the private key, and registers it with MATTR VII. They are then responsible for signing and managing any Document Signer Certificates (DSCs) and Status List Signer Certificates (SLSCs) that are required to sign mDocs and Status lists. ## Creating an IACA [#creating-an-iaca] 1. Log into the [MATTR Portal](https://portal.mattr.global/). 2. In the navigation panel on the left-hand side, expand the **Platform Management** menu. 3. Select **Certificates**. 4. Select the **Create new** button. 5. Use the *Type* radio button to select **IACA - Issuing Authority Certificate Authority**. 6. Use the *Management method* radio button to select **MATTR managed**. 7. Use the *Country* dropdown list to select an issuing country. 8. Select the **Create** button to create the IACA certificate.\ The IACA is created as *inactive* by default. 9. Use the *Status* radio button to select **Active**. 10. Select the **Update** button to activate the IACA certificate. **Create a managed IACA** Make a request of the following structure to [create a managed IACA](/docs/issuance/certificates/api-reference/iaca#create-an-iaca): ```http title:"Request" POST /v2/credentials/mobile/iacas ``` ```json title:"Request body" { "commonName": "Example IACA", "country": "US", "stateOrProvinceName": "US-AL", "notBefore": "2024-09-26", "notAfter": "2034-09-26" } ``` * `commonName` : This *optional* parameter indicates the common name of the IACA certificate. When specified, the value must be a valid `PrintableString` and cannot be an empty string. If not provided and a [custom domain](/docs/platform-management/custom-domain-overview) is configured and verified, the custom domain is used followed by the word *IACA*. If no custom domain is configured, the tenant subdomain is used instead. * `country` : This *optional* parameter indicates the issuer country. If not provided, a country is selected based on the region of the tenant subdomain cloud host. When specified, the value must be uppercase and a valid country code as per [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html). * `stateOrProvinceName`: This optional parameter indicates the issuer state or province. When specified, the value must be uppercase and a valid country code as per [ISO 3166-2](https://www.iso.org/standard/72483.html). * `notBefore` : This *optional* parameter is used to set when the IACA becomes valid and can be used to sign mDocs. This can be used alongside the `active` field to support [IACA rotation](/docs/concepts/chain-of-trust#certificates-rotation) by creating inactive IACAs and distributing them to relying parties in advance. When not provided, defaults to time of creation. * `notAfter` : This *optional* parameter is used to set the date and time when the IACA expires. When not provided, defaults to 10 years from `notBefore`, or from issuance if `notBefore` is not provided. Maximum value is 20 years from issuance. *Response* ```json filename:"Response body" { "id": "e86dd9bc-1414-4f60-aeb1-9143451424bb", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "certificateData": { "commonName": "Example IACA", "country": "US", "stateOrProvinceName": "US-AL", "notBefore": "2024-09-26T00:09:21.000Z", "notAfter": "2034-09-26T00:09:21.000Z" }, "certificateFingerprint": "57b178a6c2b8c1877dba515ad4fd60f9c805efc309287182db7debfe43a22928", "publicKeyJwk": { "kty": "EC", "crv": "P-256", "x": "kbuvX83YDOKHZMj7UXGXd3hlgsOAQe0rWTRkZriJa60", "y": "pTK_1JY8eBRiNiZEHWDsRHOP-k5ICymTvVj5AD6P2c8" }, "active": false, "isManaged": true } ``` * `id` : Unique identifier created for each IACA. You will use it to activate the IACA in the next step. * `certificatePEM` : Certificate PEM format. * `certificateData` : Key details regarding the created IACA: * `commonName` : IACA's name, as provided in the request. * `country` : IACA’s issuer country, as provided in the request. * `stateOrProvinceName` : IACA’s issuer state/province, as provided in the request. * `notBefore` : Data and time when IACA becomes valid. * `notAfter` : Date and time when IACA expires. * `certificateFingerprint` : Hashed value of the IACA certificate that includes all certificate data and its signature. * `publicKeyJwk` : JWK format of the IACA public key. * `active`: IACA status. IACAs are always created as inactive, meaning they cannot be used to sign mDocs until they are [activated](/docs/issuance/certificates/api-reference/iaca#update-an-iaca). This is useful for IACA rotation, allowing you to create a new IACA in advance and distribute it to relying parties before it becomes active. * `isManaged` : Indicates that this is a managed IACA. **Activate the IACA** Make a request of the following structure to [update the unmanaged IACA](/docs/issuance/certificates/api-reference/iaca#update-an-iaca) and activate it: ```http filename:"Request" PUT /v2/credentials/mobile/iacas/{iacaId} ``` * `iacaId` : Replace with the `id` value obtained when you registered the unmanaged IACA. ```json filename:"Request structure" { "active": true } ``` Once a managed IACA is activated, MATTR VII will automatically use it to sign DSCs required to sign mDocs. Refer to [Certificate selection for signing mDocs](/docs/issuance/certificates/overview#certificate-selection) for more information. **Generate a self-signed root certificate (IACA)** Use your preferred cryptographic library or tool to generate a self-signed root certificate (IACA). This certificate will be used to sign the Document Signer Certificates (DSCs) for mDoc issuance. Ensure the IACA meets the requirements specified in [ISO/IEC 18013-5:2021 Annex B.1.2](https://www.iso.org/standard/69084.html). When using unmanaged (external) certificates, the issuer assumes full responsibility for the secure management of the uploaded root certificates and all subordinate certificates. This includes ensuring the protection, proper issuance, and timely revocation of certificates under the uploaded root, as MATTR VII does not manage or monitor these certificates on the issuer's behalf. **Register the unmanaged IACA with MATTR VII** 1. Log into the [MATTR Portal](https://portal.mattr.global/). 2. In the navigation panel on the left-hand side, expand the **Platform Management** menu. 3. Select **Certificates**. 4. Select the **Create new** button. 5. Use the *Type* radio button to select **IACA - Issuing Authority Certificate Authority**. 6. Use the *Management method* radio button to select **Externally managed**. 7. Use the *Certificate PEM file* field to upload the PEM-encoded IACA certificate you generated in the previous step. 8. Select the **Create** button to create the IACA certificate.\ The IACA is created as *inactive* by default, and new forms are displayed to enable you to create the required child certificates in the next steps. Make a request of the following structure to [create an unmanaged IACA](/docs/issuance/certificates/api-reference/iaca#create-an-iaca): ```http filename:"Request" POST /v2/credentials/mobile/iacas ``` ```json filename:"Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0zMzA5\r\nMDgyMzM0MjJaMCIxIDAJBgNVBAYTAk5aMBMGA1UEAxMMRXhhbXBsZSBJQUNBMFkw\r\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBbK7JKKFMWuu8kHQK2qaML+MQ0Ykk3Qg\r\n/p3TC6lQKvYJozPSpLXbJQIzMPq9u/dG+j4vq1iX/G/jFIwfiEiKEqOB0TCBzjAS\r\nBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIABjAdBgNVHQ4EFgQU9zTh\r\nKsqFxAgRJDDGW1au+ewJK6owHgYDVR0SBBcwFYYTaHR0cHM6Ly9leGFtcGxlLmNv\r\nbTBpBgNVHR8EYjBgMF6gXKBahlhodHRwczovL2V4YW1wbGUuY29tL3YyL2NyZWRl\r\nbnRpYWxzL21vYmlsZS9pYWNhcy8yZTg5YzE1Ni0zMWQ1LTQ3ODMtYmQ1OS05MDU1\r\nYjVmOGU3ZDIvY3JsMAoGCCqGSM49BAMCA0gAMEUCIQDD+eU8iOsYYC0v41L94fhF\r\nZ0brPo4gx2aRxrhE3NLFpwIgIgHCPBXJ+JICJg3K7dEsr153So4SEZzAA1rRn4eF\r\nvkM=\r\n-----END CERTIFICATE-----\r\n" } ``` * `certificatePem` : This required parameter contains the PEM-encoded IACA certificate. The certificate must meet the following requirements: * Valid * Not expired * Compliant with [ISO/IEC 18013-5:2021 Annex B.1.2](https://www.iso.org/standard/69084.html) The response will include an `id` property, which is a unique identifier for the unmanaged IACA. This identifier will be used in subsequent operations to reference this unmanaged IACA. **Create a Document Signer** 1. Scroll down to the *Child certificates* area. 2. In the *Document Signer Certificate* section, select the **Add new** button. 3. Select the **Create** button in the pop up window.\ A new Document Signer Certificate (DSC) will be created, and you'll be navigated to its details screen. 4. In the *Download the DSC Certificate Signing Request* section, download the DSC CSR file.\ This file contains the PEM-encoded Certificate Signing Request (CSR) that you'll use to generate and sign the DSC in the next step. Make a request of the following structure to [create a Document Signer](/docs/issuance/certificates/api-reference/document-signers#create-a-document-signer) that references the unmanaged IACA: ```http filename:"Request" POST /v2/credentials/mobile/document-signers ``` ```json filename:"Request body" { "iacaId": "080c670a-2e90-4023-b79f-b706e55e9bc6" } ``` * `iacaId` : Replace with the `id` value obtained when you created the unmanaged IACA in the previous step. Attempts to provide a managed IACA identifier for manual Document Signer creation will result in an error. The response will include two properties which you will use later in this guide: * `id` : The unique identifier for the Document Signer. This identifier will be used in subsequent operations to reference this Document Signer. * `csrPem` : The X.509 Certificate Signing Request (CSR) in PEM format. You will use this CSR to generate a valid Document Signer Certificate in the next step. **Generate and sign the Document Signer Certificate (DSC)** Use your preferred cryptographic library or tool to generate and sign a Document Signer Certificate (DSC) using the CSR provided in the response from the previous step. Refer to the [certificate requirements](/docs/issuance/certificates/overview#certificate-requirements) section in the external issuer certificates documentation for details on how to structure a valid DSC. Because you control the validity period of unmanaged certificates, set the DSC to expire as soon as is practical for your use case. Shorter-lived signer certificates limit the impact of a key compromise. Balance this against the period your issued mDocs must remain verifiable, as an mDoc can no longer be verified once its DSC expires. For guidance on choosing validity periods, see [Certificates validity recommendations](/docs/issuance/certificates/overview#recommendations). **Associate the DSC with the Document Signer** 1. Return to the Document Signer details screen in the MATTR Portal. 2. In the *Upload signed DSC* section, use the *Certificate PEM file* field to upload the PEM-encoded DSC you created in the previous step. 3. Use the *Status* radio button to select **Active**. 4. Select the **Update** button to associate the DSC with the Document Signer. Make a request of the following structure to [update the Document Signer](/docs/issuance/certificates/api-reference/document-signers#update-a-document-signer) to activate and associate it with the generated DSC: ```http filename:"Request" PUT /v2/credentials/mobile/document-signers/{documentSignerId} ``` * `documentSignerId` : Replace with the `id` value obtained when you created the Document Signer in the previous step. ```json filename:"Request body" { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICbzCCAhSgAwIBAgIKfS7sskyJEh+DOzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0yNDA5\r\nMTAyMzM0MjJaMDExLzAJBgNVBAYTAk5aMCIGA1UEAxMbZXhhbXBsZS5jb20gRG9j\r\ndW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7fa+jv9zCtHQ\r\nmKn7o1dS6lBHD5thlhPqjlx7qEfqy8Im9AcQJDal2sr/fUxhHwf/G4ublS7AL04U\r\n73dzr/ozxaOCASEwggEdMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLdNNPTmPxt0\r\nLqvlZnV/QL86MXOxMB8GA1UdIwQYMBaAFPc04SrKhcQIESQwxltWrvnsCSuqMA4G\r\nA1UdDwEB/wQEAwIAgDAeBgNVHREEFzAVhhNodHRwczovL2V4YW1wbGUuY29tMB4G\r\nA1UdEgQXMBWGE2h0dHBzOi8vZXhhbXBsZS5jb20waQYDVR0fBGIwYDBeoFygWoZY\r\naHR0cHM6Ly9leGFtcGxlLmNvbS92Mi9jcmVkZW50aWFscy9tb2JpbGUvaWFjYXMv\r\nMmU4OWMxNTYtMzFkNS00NzgzLWJkNTktOTA1NWI1ZjhlN2QyL2NybDASBgNVHSUE\r\nCzAJBgcogYxdBQECMAoGCCqGSM49BAMCA0kAMEYCIQCfgn6+QoNfDVelJANl+Jp9\r\ncq7X9paZylfnI6UGr1FM6gIhAIzhiyclDa8+/ZSRfu7KfgGrNRaJ8YQ6vevskJls\r\nIavC\r\n-----END CERTIFICATE-----\r\n" } ``` * `active` : This required boolean indicates whether the Document Signer is active or not. Can only be set to `true` when a `certificatePem` is provided. Only active Document Signers can be used to sign mDocs. * `certificatePem` : This required parameter contains the PEM-encoded DSC created in the previous step. **Create a Status List Signer** The following steps are only required when implementing mDocs revocation capabilities. If you are not implementing revocation, you can skip to step 9. 1. Return to the IACA certificate detail screen in the MATTR Portal. 2. Scroll down to the *Child certificates* area. 3. In the *Status List Signer Certificate* section, select the **Add new** button. 4. Select the **Create** button in the pop up window.\ A new Status List Signer Certificate (SLSC) will be created, and you'll be navigated to its details screen. 5. In the *Download the SLSC Certificate Signing Request* section, download the SLSC CSR file.\ This file contains the PEM-encoded Certificate Signing Request (CSR) that you'll use to generate and sign the SLSC in the next step. Make a request of the following structure to [create a Status List Signer](/docs/issuance/revocation/api-reference/mdocs-status-list-signers#create-a-status-list-signers) that references the unmanaged IACA: ```http filename:"Request" POST /v2/credentials/mobile/status-list-signers ``` ```json filename:"Request body" { "iacaId": "080c670a-2e90-4023-b79f-b706e55e9bc6" } ``` * `iacaId` : Replace with the `id` value obtained when you created the unmanaged IACA in the previous step. Attempts to provide a managed IACA identifier for manual Status List Signer creation will result in an error. The response will include two properties which you will use later in this guide: * `id` : The unique identifier for the Status List Signer. This identifier will be used in subsequent operations to reference this Status List Signer. * `csrPem` : The X.509 Certificate Signing Request (CSR) in PEM format. You will use this CSR to generate a valid Document Signer Certificate in the next step. **Generate and sign the Status List Signer Certificate (SLSC)** Use your preferred cryptographic library or tool to generate and sign a Status List Signer Certificate (SLSC) using the CSR provided in the response from the previous step. Refer to the [certificate requirements](/docs/issuance/certificates/overview#certificate-requirements) section in the external issuer certificates documentation for details on how to structure a valid SLSC. As with the DSC, set the SLSC to expire as soon as is practical for your use case. Shorter-lived signer certificates limit the impact of a key compromise. For guidance on choosing validity periods, see [Certificates validity recommendations](/docs/issuance/certificates/overview#recommendations). **Associate the SLSC with the Status List Signer** 1. Return to the Status List Signer details screen in the MATTR Portal. 2. In the *Upload signed SLSC* section, use the *Certificate PEM file* field to upload the PEM-encoded SLSC you created in the previous step. 3. Use the *Status* radio button to select **Active**. 4. Select the **Update** button to associate the SLSC with the Status List Signer. Make a request of the following structure to [update the Status List Signer](/docs/issuance/revocation/api-reference/mdocs-status-list-signers#update-a-status-list-signer) to active and associate it with the Status List Signer: ```http filename:"Request" PUT /v2/credentials/mobile/status-list-signers/{statusListSignerId} ``` * `statusListSignerId` : Replace with the `id` value obtained when you created the Status List Signer in the previous step. ```json filename:"Request body" { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICbzCCAhSgAwIBAgIKfS7sskyJEh+DOzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0yNDA5\r\nMTAyMzM0MjJaMDExLzAJBgNVBAYTAk5aMCIGA1UEAxMbZXhhbXBsZS5jb20gRG9j\r\ndW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7fa+jv9zCtHQ\r\nmKn7o1dS6lBHD5thlhPqjlx7qEfqy8Im9AcQJDal2sr/fUxhHwf/G4ublS7AL04U\r\n73dzr/ozxaOCASEwggEdMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLdNNPTmPxt0\r\nLqvlZnV/QL86MXOxMB8GA1UdIwQYMBaAFPc04SrKhcQIESQwxltWrvnsCSuqMA4G\r\nA1UdDwEB/wQEAwIAgDAeBgNVHREEFzAVhhNodHRwczovL2V4YW1wbGUuY29tMB4G\r\nA1UdEgQXMBWGE2h0dHBzOi8vZXhhbXBsZS5jb20waQYDVR0fBGIwYDBeoFygWoZY\r\naHR0cHM6Ly9leGFtcGxlLmNvbS92Mi9jcmVkZW50aWFscy9tb2JpbGUvaWFjYXMv\r\nMmU4OWMxNTYtMzFkNS00NzgzLWJkNTktOTA1NWI1ZjhlN2QyL2NybDASBgNVHSUE\r\nCzAJBgcogYxdBQECMAoGCCqGSM49BAMCA0kAMEYCIQCfgn6+QoNfDVelJANl+Jp9\r\ncq7X9paZylfnI6UGr1FM6gIhAIzhiyclDa8+/ZSRfu7KfgGrNRaJ8YQ6vevskJls\r\nIavC\r\n-----END CERTIFICATE-----\r\n" } ``` * `active` : This required boolean indicates whether the Status List Signer is active or not. Can only be set to `true` when a `certificatePem` is provided. Only active Status List Signers can be used to sign mDocs. * `certificatePem` : This required parameter contains the PEM-encoded Status List Signer Certificate (SLSC) created in the previous step. **Activate the IACA** Once you have created and associated the required child certificates (DSC and, if applicable, SLSC), you can activate the unmanaged IACA to make it available for signing mDocs. 1. Return to the IACA certificate detail screen in the MATTR Portal. 2. Use the *Status* radio button to select **Active**. 3. Select the **Update** button to activate the IACA certificate. Make a request of the following structure to [update the unmanaged IACA](/docs/issuance/certificates/api-reference/iaca#update-an-iaca) and activate it: ```http filename:"Request" PUT /v2/credentials/mobile/iacas/{iacaId} ``` * `iacaId` : Replace with the `id` value obtained when you registered the unmanaged IACA. ```json filename:"Request body" { "active": true } ``` ## Usage [#usage] **Signing the mDoc** Once the IACA is activated, it can be used to sign mDocs. MATTR VII will automatically select a valid Document Signer based on the [certificate selection logic](/docs/issuance/certificates/overview#certificate-selection). If there is no Document Signer that meets the selection criteria, MATTR VII will automatically create a suitable one. For example, if you attempt to sign an mDoc with an expiry date later than the `notAfter` date of all available Document Signers, MATTR VII will create a new Document Signer that can accommodate the requested expiry date. **Signing the Status List Token** If the issued mDoc is configured as revocable, it will be associated with a [Status List](/docs/issuance/revocation/overview#status-list). MATTR VII will then automatically attempt to sign a Status List Token for that Status List using an appropriate Status List Signer. The Status List Signer is selected according to the following criteria: * The Status List Signer must be active. * The Status List Signer must reference the same IACA as the Document Signer used to sign the mDoc. If there is no Status List Signer that meets the selection criteria, MATTR VII will automatically create a suitable one. For example, if you attempt to sign an mDoc using a Document Signer that was created with an IACA that does not have an active Status List Signer, MATTR VII will create a new Status List Signer that meets the criteria. **Signing the mDoc** Once the Document Signer and IACA are activated, they can be used to sign mDocs. MATTR VII will automatically select a valid Document Signer based on the [certificate selection logic](/docs/issuance/certificates/overview#certificate-selection). If there is no Document Signer that meets the selection criteria, MATTR VII will return an error stating that no valid Document Signer is available for signing. For example, if you attempt to sign an mDoc with an expiry date later than the `notAfter` date of all available Document Signers, MATTR VII will not find a suitable Document Signer and will return an error. Unlike the managed IACA flow, MATTR VII does not automatically create new Document Signers in the unmanaged flow, and the issuer is responsible for manually creating and uploading them as needed. **Signing the Status List Token** If the issued mDoc is configured as revocable, it will be associated with a [Status List](/docs/issuance/revocation/overview#status-list). MATTR VII will then automatically attempt to sign a Status List Token for that Status List using an appropriate Status List Signer. The Status List Signer is selected according to the following criteria: * The Status List Signer must be active. * The Status List Signer must reference the same IACA as the Document Signer used to sign the mDoc. If there is no Status List Signer that meets the selection criteria, MATTR VII will return an error stating that no valid Status List Signer is available for signing. For example, if you attempt to sign an mDoc using a Document Signer that was created with an IACA that does not have an active Status List Signer, MATTR VII will not find a suitable Status List Signer and will return an error. Unlike the managed IACA flow, MATTR VII does not automatically create new Status List Signers in the unmanaged flow, and the issuer is responsible for manually creating and uploading them as needed. ## IACA Distribution [#iaca-distribution] IACAs can be distributed via different mechanisms, for example using a [Verified Issuer Certificate Authority List (VICAL)](/docs/digital-trust-service/vical-overview). Alternatively, all active IACAs on a given tenant can be retrieved by: 1. Making a [GET request](/docs/issuance/certificates/api-reference/issuer-metadata) to the issuer's `/.well-known/openid-credential-issuer` endpoint. 2. Inspecting the `mdoc_iacas_uri` property in the response and obtaining the IACA distribution URL. It will be structured as follows: `https://{tenant-subdomain}/core/v1/openid/iacas`. 3. Making a GET request to the IACA distribution URL. This would return a list of all active IACAs for that tenant. # Overview URL: /docs/issuance/certificates/overview ## Chain of trust [#chain-of-trust] When issuers issue mDocs they must ensure that relying parties can verify the authenticity and integrity of these credentials. This is accomplished using a [chain of trust](/docs/concepts/chain-of-trust), a hierarchy of certificates that proves the issuer’s authenticity. The following diagram depicts how MATTR implements the chain of trust model when signing mDocs: Chain of trust ### Issuing Authority Certificate Authority (IACA) [#issuing-authority-certificate-authority-iaca] The root certificate, operated by or on behalf of an issuing authority. The IACA is a X.509 certificate used to identify an mDoc issuer and verify the mDocs they issue. An IACA can be valid for up to 20 years, and is used to sign Document Signer Certificates (DSCs), which are then in turn used to sign Mobile Security Objects (MSO) in mDocs. Refer to [Certificate selection for signing mDocs](#certificate-selection-for-signing-mdocs) below to learn more about how MATTR VII automatically selects a suitable IACA when signing mDocs. Each IACA includes the following elements: * **Signature**: As the IACA is the root certificate, it is self-signed and verified against its own public key also in the certificate. * **Public key**: Used to verify the IACA’s certificate signature, as well as the DSC signature. The private key linked to the certificate is stored and managed in the highly secure and reliable Key Management System (KMS). * **Validity information:** When the certificate becomes valid or expires, as well as information about how to check its revocation status. IACAs can be distributed via different mechanisms, for example using a [Verified Issuer Certificate Authority List (VICAL)](#verified-issuer-certificate-authority-list-vical). Alternatively, all active IACAs on a given tenant can be retrieved by: 1. Making a [GET request](/docs/issuance/certificates/api-reference/issuer-metadata) to the issuer's `/.well-known/openid-credential-issuer` endpoint. 2. Inspecting the `mdoc_iacas_uri` property in the response and obtaining the IACA distribution URL. It will be structured as follows: `https://{tenant-subdomain}/core/v1/openid/iacas`. 3. Making a GET request to the IACA distribution URL. This would return a list of all active IACAs for that tenant. ### Document Signer Certificate (DSC) [#document-signer-certificate-dsc] The end-entity certificate. The DSC is a X.509 certificate used to digitally sign Mobile Security Objects (MSOs) in mDocs. The DSC itself must be issued and signed by the root certificate, which is the Issuing Authority Certificate Authority (IACA). To understand how mDocs are being signed, it is important to differentiate between a Document Signer and the Document Signer Certificate (DSC). The Document Signer is a logical entity that represents the device or application that signs the mDoc. It is responsible for signing the MSO payload contained within the mDoc. The DSC, on the other hand, is the actual digital certificate that authenticates the Document Signer and is used to verify the signature of the MSO To sign an mDoc, you must use an active Document Signer that is linked to a valid DSC. The DSC is embedded within the [signed mDoc](/docs/concepts/mdocs/structure-to-function), so verifiers only need to obtain the IACA to validate the mDoc. For details on how MATTR VII automatically manages DSC generation and selection during mDoc signing, see [Certificate selection for signing mDocs](#certificate-selection-for-signing-mdocs) below. Each DSC includes the following elements: * **Signature**: DSCs are signed by the IACA, and this signature is verified against the IACA’s public key. * **Public key**: Used to verify the MSO signature. * **Validity information:** When the certificate becomes valid or expires, as well as information about how to check its revocation status. The ISO/IEC 18013:5:2021 standard determines that a chain of certificates that is used to sign an mDL can only include one IACA and one end-entity certificate, which is the DSC. Other mDocs do not have that limitation and can have more than one intermediate certificate, linking the IACA to the end-entity certificate. ### Mobile Security Object (MSO) [#mobile-security-object-mso] The end-entity of the chain of trust, which is the issued signed data. mDocs include an `IssuerAuth` data structure, as defined in the [ISO/IEC 18013-5:2021 standard](https://www.iso.org/standard/69084.html). It contains the MSO payload, which comprises metadata and salted hashed claims. Each MSO includes the following elements: * **Signature**: MSOs are signed by the DSC. Their signature is checked against the DSC public key, and the DSC is validated all the way up to the IACA. * **Public key**: Used to verify the signature of the device the mDoc was issued to, enabling device authentication. * **Validity information:** When the MSO becomes valid or expires, as well as information about how to check its revocation status. ## Supported cryptographic algorithms and curves [#supported-cryptographic-algorithms-and-curves] MATTR VII supports different cryptographic algorithms and curves for IACAs and DSCs, depending on whether the issuer is using MATTR VII managed certificates or [unmanaged (external) certificates](/docs/concepts/chain-of-trust#external-certificates). ### Managed IACAs and DSCs [#managed-iacas-and-dscs] Managed IACAs and DSCs **only** support **ECDSA with `P-256` and SHA-256** as the signature algorithm. Within X.509 certificates, this is represented by the OID `1.2.840.10045.4.3.2` (`ecdsa-with-SHA256`). ### Unmanaged IACAs [#unmanaged-iacas] Unmanaged IACAs support the following elliptic curve signature algorithms and curves: #### ECDSA [#ecdsa] The **Elliptic Curve Digital Signature Algorithm (ECDSA)** is supported with the following curves: * `P-256` * `P-384` * `P-521` * `brainpoolP256r1` * `brainpoolP320r1` * `brainpoolP384r1` * `brainpoolP512r1` #### EdDSA [#eddsa] The **Edwards-curve Digital Signature Algorithm (EdDSA)** is supported with the following curves: * `Ed25519` * `Ed448` ### Compatibility considerations [#compatibility-considerations] When using managed certificates, ECDSA with `P-256` curve is the only supported option for both IACAs and DSCs. This choice is to ensure maximum compatibility across all verifiers and holders, given the P-256 curve is the most widely supported. This includes MATTR's own Pi Verifier and Holder SDKs, as well as third-party implementations. When using unmanaged certificates (for example when [creating an unmanaged IACA](/docs/issuance/certificates/guide#unmanaged-1)), you must ensure the selected algorithm and curve are supported by the verifiers and holders that will interact with the mDocs you issue. If you select a curve that is not widely supported, some holders may not be able to store the mDoc in their digital wallet, and some verifiers may not be able to verify it. If your implementation is using MATTR Pi Holder and/or Verifier SDKs, you can refer to the mDocs [Holder](/docs/holding/sdk-overview#isoiec-18013-5) and [Verifier](/docs/verification/sdks/overview#supported-isoiec-18013-5-features) SDK documentation for details on the algorithms and curves they support. ## Certificates validity [#certificates-validity] The concept of certificates validity is of paramount importance in the context of mDocs. For an mDoc to be verified, all the certificates within its chain of trust must be valid. If during the verifications process any of the certificates have expired, the mDoc cannot be verified: Certificate validity timeline Careful analysis must be conducted before determining the certificate lifecycle. IACAs are usually created with long expiry dates, some as long as 10-15 years, while DSCs and SLSCs tend to have shorter validity periods. There are a variety of trade-offs in making this duration longer or shorter. Shorter validity periods improve security. They limit the window in which a compromised private key can be misused, and they reduce the number of credentials affected if a key is exposed. Shorter periods also enable faster key rotation intervals and provide the opportunity to change the cryptographic algorithms being used if required. On the other hand, short validity periods come with increased deployment overhead. Whenever a new IACA is issued, the certificate must be distributed to all verifiers that verify mDocs from that issuer. Understanding the unique needs and requirements of ecosystems is crucial to applying correct certificates validity periods. ### Recommendations [#recommendations] As a general principle, set each certificate's validity period to the shortest duration that remains practical for your ecosystem, balancing the security benefits of shorter-lived certificates against the operational overhead of rotating and redistributing them. This is particularly important for signer certificates (DSCs and SLSCs). These end-entity certificates directly sign issued data (DSCs sign mDocs, and SLSCs sign status lists), so a compromise of their private keys carries the most immediate impact. This guidance is most relevant when using [unmanaged (external) certificates](/docs/concepts/chain-of-trust#external-certificates), where you control the validity periods of your IACAs, DSCs, and SLSCs directly. When using managed certificates, MATTR VII already issues short-lived DSCs automatically and rotates them on your behalf, in line with the [certificate selection logic](#certificate-selection). When choosing a validity period, consider the following: * **Signer certificates (DSCs and SLSCs)**: Favor shorter validity periods. Frequent rotation limits the exposure of a compromised signer key. Rotation overhead is comparatively low for DSCs, as a DSC is embedded in the mDocs it signs and does not need to be distributed to verifiers separately. * **IACAs**: Balance the security benefits of a shorter validity period against the overhead of distributing each new IACA to all relying parties that verify your mDocs. Plan rotation in advance so that a replacement IACA can be distributed before the current one expires. See [Certificates rotation](/docs/concepts/chain-of-trust#certificates-rotation) for more detail. ## Certificate selection [#certificate-selection] When signing an mDoc and issuing it to a holder, MATTR VII automatically selects IACA and DSC certificates to ensure a valid mDoc is issued. The selection criteria is consistent whether the issuer is using MATTR VII managed or [unmanaged (external) certificates](/docs/concepts/chain-of-trust#external-certificates). ### IACA selection [#iaca-selection] MATTR VII will first attempt to select an IACA that is: * Active. * Valid for the required time period: * Credential cannot be valid before the IACA becomes valid. * Credential cannot be valid after the IACA expires. If no IACAs meet these requirements, issuance will fail. ### DSC selection [#dsc-selection] After selecting an IACA, MATTR VII examines all existing Document Signers with DSCs signed by that IACA. It then tries to select a Document Signer that is: * Active. * Its DSC is valid for the required time period: * Credential cannot be valid before the DSC becomes valid. * Credential cannot be valid after the DSC expires. If multiple DSCs meet these requirements, the most recently created one will be used. If no existing DSCs meet these requirements and the issuer is using [unmanaged (external) issuer certificates](/docs/concepts/chain-of-trust#external-certificates), issuance will fail. If no existing DSCs meet these requirements and the issuer is using managed certificates, MATTR VII will automatically generate a new DSC that meets the following criteria: * Valid from time of creation. * Valid until the later of the following: * 30 days from the DSC creation date. * 30 days from the signed mDoc expiry date. * Regardless of these validity settings: * The DSC cannot be valid after the IACA used to sign it expires. * The DSC cannot be valid for more than 10 years (3650 days). * The DSC cannot be valid for more than 457 days if the signed mDoc is an mDL (as indicated by the [credential configuration](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=type\&t=request) `type`). * Suitable for signing the requested mDoc type (DSCs generated by MATTR VII can sign any type of mDoc). DSCs generated by MATTR can be used to sign any mDocs format. If your verification system enforces strict Extended Key Usage (EKU) validation, ensure it supports multiple key usages as appropriate. ## Certificate revocation [#certificate-revocation] Managed DSCs may be [revoked](/docs/issuance/certificates/api-reference/document-signers#revoke-a-document-signer) for reasons such as key compromise, role change, or decommissioning. Once revoked, the DSC’s serial number is published in the Certificate Revocation List (CRL) referenced by its issuing IACA. Verifiers that retrieve and process the CRL referenced in the IACA must treat a revoked DSC, and any mDocs it signed, as invalid. Revocation through MATTR VII is only available when the IACA is managed by MATTR VII, as MATTR VII holds the IACA private key and signs the CRL on your behalf. For unmanaged (external) IACAs, you must revoke the certificate directly with the CA that issued it. For step-by-step instructions, see [How to revoke signing certificates](/docs/issuance/certificates/revoke-signing-certificates). ## Certificate requirements [#certificate-requirements] The following lists depicts the requirements for external certificates used in MATTR VII. Some of the requirements are common across all certificates, while others are specific to the type of certificate (IACA, DSC, SLSC). ### Common certificate requirements [#common-certificate-requirements] * Certificate format & basic attributes: * PEM format must contain a valid X.509 certificate. * Version must be v3. * `Issuer` field must be present and valid. * Issuer Alternative Name must be present and contain a valid email address or URI. * Serial Number: * Must be present. * Must contain 1-20 digits (**Best practice**: Use a positive, non-sequential value). * Subject attributes: * Subject field must be present: * Country (C): must be present and be a valid [ISO 3166-1 alpha-2 code](https://www.iso.org/glossary-for-iso-3166.html). * Common Name (CN): must be present. * If State or Province (ST) is present, it must be a valid [ISO 3166-2 code](https://www.iso.org/glossary-for-iso-3166.html) and match the Country (C). * Public Key requirements: * Subject Public Key must be present. * Extensions: * Certificate must include extensions. * Duplicate extensions are not allowed (No more than one extension with the same `extnID`). * Mandatory extensions: * Subject Key Identifier: Must be present and non-empty. * Key Usage: * Must be present. * Must match the intended use of the certificate (e.g. IACA, DSC, SLSC). * Validity period: * `NotAfter` must be after `NotBefore`. * `NotAfter` cannot be in the past (expired certificates are invalid). * Future dated certificates are valid (e.g. `notBefore` can be in the future). * Must be within allowed limits for certificate type: * IACA: Maximum 20 years from issuance. * DSC/SLSC: Maximum 10 years from issuance. * Certificate Revocation List (CRL): * If a CRL is provided, it must be valid and signed by the IACA. * The CRL must be accessible via a valid URI. ### IACA specific requirements [#iaca-specific-requirements] * Must include the `keyCertSign` and `cRLSign` key usages. * Basic constraints must be present and `CA` must be set to `TRUE`. * Issuer Alternative Name must be present and contain a valid email address or URI. * Status List Distribution URI must be valid, if present: * OID: `1.3.6.1.4.1.61546.100` * Value: Must point to a valid location where the Status List can be retrieved (e.g. `https://{tenant-subdomain}/v2/credentials/mobile/status-lists/distribution`). * Signature must be self-signed and verifiable. * Public key must use one of the supported public key algorithms and curves as defined in ISO/IEC 18013-5:2021 B.3: * ECDSA curves: `P-256`, `P-384`, `P-521`, `brainpoolP256r1`, `brainpoolP320r1`, `brainpoolP384r1`, `brainpoolP512r1` * EdDSA key types: `Ed25519`, `Ed448` ### DSC/SLSC specific requirements [#dscslsc-specific-requirements] * Must be signed by a valid IACA. * Common name must differ from parent/root IACA. * `Issuer` field must be present and must match the exact binary value of the IACA certificate subject. * Must include the `digitalSignature` key usage exclusively. * Extended key usage must be present and include the correct OID: * For DSCs: * `1.0.18013.5.1.2` (required, used for signing mDLs). * `1.3.6.1.4.1.61546.0` (optional, used for signing any other mDocs). * For SLSCs: `1.3.6.1.4.1.61546.1`. * Signature must be verifiable against the IACA. * Authority Key Identifier must be present and match the IACA's Subject Key Identifier. * Must not exceed parent IACA's validity period (i.e. `notBefore` and `notAfter` must be within the IACA's validity period). * Public key must match the CSR provided during Document/Status List Signer creation. ### Example certificates [#example-certificates] See an example of valid certificates parsed using the MATTR Labs [X.509 certificate decoder](https://tools.mattrlabs.com/pem): * [IACA](https://tools.mattrlabs.com/pem?cert=MIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQGEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0zMzA5MDgyMzM0MjJaMCIxIDAJBgNVBAYTAk5aMBMGA1UEAxMMRXhhbXBsZSBJQUNBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBbK7JKKFMWuu8kHQK2qaML%252BMQ0Ykk3Qg%252Fp3TC6lQKvYJozPSpLXbJQIzMPq9u%252FdG%252Bj4vq1iX%252FG%252FjFIwfiEiKEqOB0TCBzjASBgNVHRMBAf8ECDAGAQH%252FAgEAMA4GA1UdDwEB%252FwQEAwIABjAdBgNVHQ4EFgQU9zThKsqFxAgRJDDGW1au%252BewJK6owHgYDVR0SBBcwFYYTaHR0cHM6Ly9leGFtcGxlLmNvbTBpBgNVHR8EYjBgMF6gXKBahlhodHRwczovL2V4YW1wbGUuY29tL3YyL2NyZWRlbnRpYWxzL21vYmlsZS9pYWNhcy8yZTg5YzE1Ni0zMWQ1LTQ3ODMtYmQ1OS05MDU1YjVmOGU3ZDIvY3JsMAoGCCqGSM49BAMCA0gAMEUCIQDD%252BeU8iOsYYC0v41L94fhFZ0brPo4gx2aRxrhE3NLFpwIgIgHCPBXJ%252BJICJg3K7dEsr153So4SEZzAA1rRn4eFvkM%253D) * [DSC](https://tools.mattrlabs.com/pem?cert=MIICbzCCAhSgAwIBAgIKfS7sskyJEh%252BDOzAKBggqhkjOPQQDAjAiMSAwCQYDVQQGEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0yNDA5MTAyMzM0MjJaMDExLzAJBgNVBAYTAk5aMCIGA1UEAxMbZXhhbXBsZS5jb20gRG9jdW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7fa%252Bjv9zCtHQmKn7o1dS6lBHD5thlhPqjlx7qEfqy8Im9AcQJDal2sr%252FfUxhHwf%252FG4ublS7AL04U73dzr%252FozxaOCASEwggEdMAwGA1UdEwEB%252FwQCMAAwHQYDVR0OBBYEFLdNNPTmPxt0LqvlZnV%252FQL86MXOxMB8GA1UdIwQYMBaAFPc04SrKhcQIESQwxltWrvnsCSuqMA4GA1UdDwEB%252FwQEAwIAgDAeBgNVHREEFzAVhhNodHRwczovL2V4YW1wbGUuY29tMB4GA1UdEgQXMBWGE2h0dHBzOi8vZXhhbXBsZS5jb20waQYDVR0fBGIwYDBeoFygWoZYaHR0cHM6Ly9leGFtcGxlLmNvbS92Mi9jcmVkZW50aWFscy9tb2JpbGUvaWFjYXMvMmU4OWMxNTYtMzFkNS00NzgzLWJkNTktOTA1NWI1ZjhlN2QyL2NybDASBgNVHSUECzAJBgcogYxdBQECMAoGCCqGSM49BAMCA0kAMEYCIQCfgn6%252BQoNfDVelJANl%252BJp9cq7X9paZylfnI6UGr1FM6gIhAIzhiyclDa8%252B%252FZSRfu7KfgGrNRaJ8YQ6vevskJlsIavC) * [SLSC](https://tools.mattrlabs.com/pem?cert=MIIB%252BDCCAZ6gAwIBAgIUbDN4Ed27snUvA3ehITvqiib6iyAwCgYIKoZIzj0EAwIwJDEVMBMGA1UEAwwMRXhhbXBsZSBJQUNBMQswCQYDVQQGEwJOWjAeFw0yNTA2MjYyMjU4NDBaFw0yNjA2MjYyMjU4NDBaMDMxCzAJBgNVBAYTAk5aMSQwIgYDVQQDDBtFeGFtcGxlIFN0YXR1cyBMaXN0IFNpZ25lciAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARm5gwI2YzENXu4a4OS%252Bj3ym%252FhomivivOo1UDMNcdxGbjT5mrOgUv7B6hppPeHWLDl4gH9BspMkAtGptz8%252FTr%252FSo4GeMIGbMB8GA1UdIwQYMBaAFPfKMlWR%252ByWXdks8axVp5E2dKnUbMDAGA1UdEgQpMCeBJXRlc3RpYWNhQGFsdGVybmF0aXZlbmFtZS5tYXR0ci5nbG9iYWwwDgYDVR0PAQH%252FBAQDAgeAMBcGA1UdJQEB%252FwQNMAsGCSsGAQQBg%252BBqATAdBgNVHQ4EFgQUyXRX0QAvlnl7rKZ6OKZq%252F3o6WkAwCgYIKoZIzj0EAwIDSAAwRQIgWwoF5tG1mlMQQXc9jiOYW%252Fcfgf%252Bv9H70J1OwDyebY%252FECIQCBbqbrqbX2UhkvHSLRAvvAjnSodkCnljadOQTTj0f6TQ%253D%253D) # How to revoke signing certificates URL: /docs/issuance/certificates/revoke-signing-certificates ## Overview [#overview] This guide explains how to revoke the signing certificates that sit below your [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) in the [chain of trust](/docs/concepts/chain-of-trust): * **[Document Signer Certificates (DSCs)](/docs/issuance/certificates/overview#document-signer-certificate-dsc)**, which sign the Mobile Security Objects (MSOs) in your mDocs. * **Status List Signer Certificates (SLSCs)**, which sign the [Status Lists](/docs/issuance/revocation/overview#status-list) used for mDoc revocation. When a signer can no longer be trusted, you revoke it so relying parties stop trusting the mDocs or Status Lists it signed. Revoking a signer publishes the certificate's serial number to the Certificate Revocation List (CRL) referenced by its issuing IACA. Verifiers that retrieve and process that CRL should treat the revoked signer, and anything it signed, as untrusted. Revocation does not delete already-issued mDocs, it withdraws trust in them at the certificate level. For background on how the IACA, DSC, and Mobile Security Object (MSO) fit together, see [Chain of trust](/docs/concepts/chain-of-trust) and the [Certificates overview](/docs/issuance/certificates/overview#chain-of-trust). ## When to revoke a signer [#when-to-revoke-a-signer] Revocation is the right tool when a signing certificate must be distrusted ahead of its natural expiry. Common scenarios include: * **Key compromise**: the private key associated with the signer is suspected or confirmed to be exposed. Anything signed by that key can no longer be trusted, so the signer is revoked immediately. * **Role or authorization change**: the signer was operated by a team, environment, or partner that is no longer authorized to issue on your behalf. * **Misissuance**: the signer was used to sign mDocs in error, or was misconfigured, and you want to invalidate its output. You do not need to revoke a signer that is simply being rotated or is expiring on schedule. For managed IACAs, MATTR VII rotates Document Signers for you and selects a valid signer at signing time, so retiring an old signer does not require revocation. Revocation is for distrusting a signer before its validity period ends. ## Prerequisites [#prerequisites] * **Managed IACA**: the Document Signer or Status List Signer you want to revoke must have been signed by a [managed IACA](/docs/issuance/certificates/guide#managed-1). When using [unmanaged (external) IACAs](/docs/concepts/chain-of-trust#external-certificates), MATTR VII does not hold the IACA private key and cannot sign a CRL for you. In that case you must revoke the signer certificate directly with the CA that issued it, following your own PKI processes. * **Permissions**: the API client needs the `admin` or `issuer` role. * **The signer identifier**: the `id` of the Document Signer or Status List Signer you want to revoke. You can obtain it from the response when the signer was created, or by [listing your Document Signers](/docs/issuance/certificates/api-reference/document-signers#retrieve-all-document-signers) or [listing your Status List Signers](/docs/issuance/revocation/api-reference/mdocs-status-list-signers#retrieve-all-status-list-signers). ## How revocation and the CRL work [#how-revocation-and-the-crl-work] When MATTR VII creates a managed IACA, it embeds a CRL Distribution Point (CDP) in the IACA certificate. The CDP points to a public, MATTR VII hosted distribution endpoint for that IACA. You can see this in the example certificates parsed with the MATTR Labs [X.509 certificate decoder](https://tools.mattrlabs.com/pem) linked from the [Certificates overview](/docs/issuance/certificates/overview#example-certificates). When you revoke a signer: 1. MATTR VII adds the signer certificate's serial number to the CRL for the issuing IACA. 2. MATTR VII signs the CRL with the IACA key and serves it from the distribution endpoint embedded in the IACA certificate. 3. Verifiers that follow the CDP retrieve the IACA-signed CRL and check the serial numbers of the certificates in the mDoc chain against it. The CRL distribution endpoint is public and does not require authentication, because relying parties must be able to reach it to validate certificates. You can retrieve the current CRL for an IACA in DER binary format: ```http title:"Request" GET /v2/credentials/mobile/iacas/{iacaId}/crl ``` See [Retrieve IACA CRL](/docs/api-reference/platform/iaca/get-mobile-credential-iaca-crl) for the full endpoint reference. ## Revoke a Document Signer [#revoke-a-document-signer] Make a request of the following structure to [revoke a Document Signer](/docs/issuance/certificates/api-reference/document-signers#revoke-a-document-signer): ```http title:"Request" POST /v2/credentials/mobile/document-signers/{documentSignerId}/revoke ``` * `documentSignerId`: Replace with the `id` of the Document Signer you want to revoke. The request body is empty: ```json title:"Request body" {} ``` ```json title:"Response body" { "revoked": true, "revocationDate": "2025-10-31T23:59:59Z" } ``` * `revoked`: Indicates that the Document Signer has been revoked. * `revocationDate`: ISO 8601 timestamp (UTC) indicating when the Document Signer was revoked. Revocation is **permanent**. A signer cannot be un-revoked, and attempting to revoke a signer that is already revoked returns a `409` response. Before revoking a signer that is actively in use, make sure a valid replacement signer is available so issuance is not interrupted. For managed IACAs, MATTR VII automatically generates a suitable Document Signer at signing time when none of the existing signers meet the [selection criteria](/docs/issuance/certificates/overview#dsc-selection). ### Example: revoking a compromised signer [#example-revoking-a-compromised-signer] Suppose you operate a managed IACA and discover that the host running one of your signing integrations may have leaked a Document Signer private key. You retrieve the affected signer's `id`, then revoke it: ```bash title:"Example request" curl -X POST \ "https://{tenant-subdomain}.vii.mattr.global/v2/credentials/mobile/document-signers/d2c9f2aa-fc69-4fbc-9b85-0c00591d72f6/revoke" \ -H "Authorization: Bearer {access-token}" \ -H "Content-Type: application/json" \ -d '{}' ``` Once revoked, the DSC serial number is published to the IACA CRL. New issuance will no longer select the revoked signer (see [Certificate selection](/docs/issuance/certificates/overview#certificate-selection)), and verifiers that process the CRL should treat the mDocs it signed as untrusted. ## Revoke a Status List Signer [#revoke-a-status-list-signer] The same model applies to Status List Signer Certificates (SLSCs), which sign the [Status Lists](/docs/issuance/revocation/overview#status-list) used for mDoc revocation. If a Status List Signer is compromised or must otherwise be distrusted, you can revoke it the same way, provided it was issued under a managed IACA: ```http title:"Request" POST /v2/credentials/mobile/status-list-signers/{statusListSignerId}/revoke ``` As with a Document Signer, send an empty JSON object (`{}`) as the request body. The response mirrors the Document Signer response with a `revoked` flag and a `revocationDate`. Revocation is permanent, and revoking a Status List Signer that is already revoked returns a `409` response. Verifiers that process the CRL should treat the revoked Status List Signer, and any Status List it signed, as untrusted. See [Revoke a status list signer](/docs/issuance/revocation/api-reference/mdocs-status-list-signers#revoke-a-status-list-signer) for the full endpoint reference. ## Revoking under unmanaged (external) IACAs [#revoking-under-unmanaged-external-iacas] When your IACA is unmanaged, MATTR VII integrates the certificates you provide but does not hold the IACA private key, so it cannot sign or publish a CRL on your behalf. To distrust a signer under an unmanaged IACA, revoke the certificate directly with the CA that issued it and publish the updated, IACA-signed CRL at the location referenced in your IACA certificate. The customer remains responsible for the full lifecycle of external certificates, including renewal and revocation. For more detail, see [External certificates](/docs/concepts/chain-of-trust#external-certificates). # API Reference URL: /docs/issuance/claims-source/api-reference ## Configure a Claims source [#configure-a-claims-source] ## Retrieve all Claim sources [#retrieve-all-claim-sources] ## Retrieve a Claim source [#retrieve-a-claim-source] ## Update a Claim source [#update-a-claim-source] ## Delete a Claim source [#delete-a-claim-source] # How to configure a Claims source URL: /docs/issuance/claims-source/guide ## Introduction [#introduction] A [Claims source](/docs/issuance/claims-source/overview) lets MATTR VII fetch credential claims from your own data system at issuance time. Two configuration decisions shape how well that integration behaves in production: how MATTR VII authorizes with your endpoint, and how it tells your endpoint which record to look up. This guide walks through the practical configuration patterns for both. It assumes you have already read the [Claims source overview](/docs/issuance/claims-source/overview) and have a backing endpoint that meets the [requirements](/docs/issuance/claims-source/overview#requirements). For an end-to-end walkthrough using a sample app, see the [Claims source tutorial](/docs/issuance/claims-source/tutorial). ## Prerequisites [#prerequisites] * A publicly accessible HTTPS endpoint that returns claims as JSON. * An OID4VCI flow (either [Authorization Code](/docs/issuance/authorization-code/overview) or [Pre-authorized Code](/docs/issuance/pre-authorized-code/overview)) in place, so you know which claims are available to map. * A [Credential configuration](/docs/issuance/credential-configuration/overview) that will reference the claims source. ## Guide overview [#guide-overview] Configuring a claims source involves three concerns: 1. **[Authorize the request](#authorize-the-request):** Pick the authorization method that matches how your backing system is protected. 2. **[Map request parameters](#map-request-parameters):** Decide which values MATTR VII should pass to your endpoint, and where they come from. 3. **[Error-proof your queries with default values](#error-proof-your-queries-with-default-values):** Make the configuration resilient to missing or unexpected claims. The examples below use the [Configure a claims source](/docs/issuance/claims-source/api-reference#configure-a-claims-source) endpoint. The same fields are available in the MATTR Portal under **Credential Issuance > Claims sources**. ### Authorize the request [#authorize-the-request] MATTR VII supports two authorization methods. Choose based on how your data system already protects access, not on which is easier to set up. Reusing your existing access control is almost always the right call. #### API key [#api-key] Use `api-key` when your endpoint is protected by a static shared secret passed in a custom header. MATTR VII sends the configured value in an `x-api-key` header on every request. ```json title="API key authorization" { "name": "Customer records", "url": "https://records.example.com/claims", "authorization": { "type": "api-key", "value": "6hrFDATxrG9w14QY9wwnmVhLE0Wg6LIvwOwUaxz761m1J" }, "requestParameters": { "customerId": { "mapFrom": "claims.sub" } } } ``` When this is a good fit: * You control the endpoint and can place it behind a key-checking gateway. * The endpoint serves only the claims source use case, so a single shared secret is acceptable. * You need a quick path to production without standing up an authorization server. When to avoid it: if your organization already mandates OAuth for service-to-service traffic, do not introduce a parallel key-based path just for claims sources. Rotate the key periodically, and never reuse a key that protects other systems. MATTR VII does not validate the key when you save the configuration. A typo will not surface until the first issuance attempt, which fails the flow. Test the configuration with a real issuance before rolling it out. #### OAuth 2.0 client credentials [#oauth-20-client-credentials] Use `oauth-client-credentials` when your endpoint is protected by an OAuth 2.0 authorization server (Auth0, Okta, Azure AD, Cognito, Keycloak, your own AS). MATTR VII performs the client credentials grant against your `tokenEndpoint`, then attaches the resulting bearer token as an `Authorization: Bearer ` header on every claims source request. ```json title="OAuth client credentials, basic auth method" { "name": "Internal CRM", "url": "https://crm.example.com/api/claims", "authorization": { "type": "oauth-client-credentials", "tokenEndpoint": "https://auth.example.com/oauth/token", "clientId": "afd16fec-8131-4f0c-8f20-1cd5d67f8e29", "clientSecret": "1b41186347e4cc716155155cdecbded07536d0f5", "tokenEndpointAuthMethod": "client_secret_basic", "audience": "https://crm.example.com", "scope": "claims:read" }, "requestMethod": "POST", "requestParameters": { "customerId": { "mapFrom": "claims.sub" } } } ``` The `tokenEndpointAuthMethod` controls how MATTR VII presents the client credentials to your authorization server: * `client_secret_basic` (default): credentials are sent in a Base64-encoded `Authorization` header on the token request. Use this with most off-the-shelf authorization servers; it is the OAuth 2.0 default. * `client_secret_post`: credentials are sent in the token request body. Use this only when your authorization server does not accept basic auth on the token endpoint (some legacy or self-hosted servers). Two optional fields tighten the issued token to the right resource: * `audience`: set this when your authorization server scopes tokens by audience (Auth0, Azure AD). The value must match what the claims source endpoint expects in the `aud` claim. * `scope`: set this when your endpoint requires a specific scope (for example `claims:read`). Omit it if your AS issues unscoped service tokens. When this is a good fit: * Your data system already lives behind your enterprise identity provider. * You need centralized credential rotation, revocation, and audit. * Different downstream systems (claims source, webhooks, internal APIs) should be authorized independently. MATTR VII fetches a fresh token for each issuance request and does not currently cache tokens between requests. Make sure your authorization server can absorb that traffic, and use a dedicated client for the claims source so rate-limiting and rotation can be scoped to it. ### Map request parameters [#map-request-parameters] `requestParameters` is the object MATTR VII sends to your endpoint. When `requestMethod` is `GET` (default), each top-level key becomes a query string parameter. When `requestMethod` is `POST`, the object is sent as a JSON request body. The keys you choose define the contract with your endpoint; pick names that match what your endpoint expects to receive. Each parameter is configured in one of three shapes: | Shape | Fields | Behavior | | --------------------- | ---------------------------- | ---------------------------------------------------------------------------------------- | | Dynamic mapping | `mapFrom` | Resolved at issuance time from the named path. If the path is missing, no value is sent. | | Dynamic with fallback | `mapFrom` and `defaultValue` | Resolved from the path. If the path is missing, `defaultValue` is used instead. | | Static value | `defaultValue` | The fixed value is sent on every request, regardless of holder context. | The rest of this step shows the data sources you can map from and the situations they fit. #### Map from `claims` [#map-from-claims] The `claims` object holds every claim gathered during the issuance flow: * In an Authorization Code flow, this is the union of claims returned by the [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) and any claims added by an [Interaction hook](/docs/issuance/authorization-code/interaction-hook/overview). * In a Pre-authorized Code flow, this is the `claims` object you set when creating the [Credential offer](/docs/issuance/pre-authorized-code/overview). This is the most common source: it is how you pass a holder-specific lookup key (subject ID, email, employee number, license number) to your endpoint. ```json title="Look up a holder by an authentication provider subject" { "requestParameters": { "subjectId": { "mapFrom": "claims.sub" } } } ``` ```json title="Look up a holder by a domain-specific identifier from an Interaction hook" { "requestParameters": { "employeeNumber": { "mapFrom": "claims.employee_number" } } } ``` The path is dot-notation, so nested claims work the same way: `claims.profile.country` resolves to the `country` property of the `profile` claim. The exact paths inside `claims` depend on how each component (Authentication provider, Interaction hook, Credential offer) is configured to expose claims. Inspect the analytics event payload from a test issuance if you are unsure what is available. #### Map from `authenticationProvider` [#map-from-authenticationprovider] This object exposes metadata about the Authentication provider used in an Authorization Code flow: * `authenticationProvider.url`: the configured provider URL. * `authenticationProvider.subjectId`: the unique identifier of the authenticated user with the provider. * `authenticationProvider.providerId`: the unique identifier of the provider configuration on your tenant. Use it when your endpoint expects the canonical subject identifier rather than a claim returned in the ID token, or when the endpoint serves multiple tenants and needs the provider context to route the lookup. ```json title="Use the provider subject as the lookup key" { "requestParameters": { "subjectId": { "mapFrom": "authenticationProvider.subjectId" }, "issuer": { "mapFrom": "authenticationProvider.url" } } } ``` This source is not populated in Pre-authorized Code flows. #### Map from `credentialConfiguration` [#map-from-credentialconfiguration] This object exposes the credential configuration being issued: * `credentialConfiguration.id`: unique configuration identifier. * `credentialConfiguration.type`: the credential type (for example `org.iso.18013.5.1.mDL`). * `credentialConfiguration.profile`: the credential format (`mobile` for mDocs, `compact` for CWT, `compact-semantic` for semantic CWT). This is useful when a single claims source backs several credential types and your endpoint needs to know which record set or schema to return. ```json title="Route the query by credential type" { "requestParameters": { "holderId": { "mapFrom": "claims.sub" }, "credentialType": { "mapFrom": "credentialConfiguration.type" } } } ``` Your endpoint can then branch internally: returning driving privileges for an mDL request and academic transcripts for a study record request, against the same holder ID. Mapping from `credentialConfiguration` is only useful if your endpoint actually understands the values. The `type` and `profile` strings come straight from your MATTR VII configuration. Coordinate the values with whoever owns the backing data system. #### Map from `wallet` [#map-from-wallet] The `wallet` object exposes information about the wallet making the credential request: * `wallet.id`: the wallet's client identifier. Use it to apply per-wallet logic, for example returning a richer claim set to a wallet you have a direct partnership with. * `wallet.instanceId`: the unique wallet instance identifier from the attestation JWT. Only populated when [Wallet Attestation](/docs/issuance/credential-issuance/wallet-attestation) is enabled. Use it when your endpoint stores per-device state (renewal counter, device binding, instance-specific entitlements). The same identifiers are exposed across both OID4VCI and Apple Wallet issuance flows, so a single `wallet` mapping works regardless of which protocol the wallet used to engage the issuer. ```json title="Per-wallet and per-instance lookup" { "requestParameters": { "holderId": { "mapFrom": "claims.sub" }, "walletId": { "mapFrom": "wallet.id" }, "walletInstanceId": { "mapFrom": "wallet.instanceId" } } } ``` A legacy `client` object is also available, exposing `client.instanceId` only. It is kept for backwards compatibility with earlier configurations and is on track for removal. Use `wallet` in new configurations and migrate existing `client.instanceId` mappings to `wallet.instanceId`. #### Map from `issuanceProtocol` [#map-from-issuanceprotocol] `issuanceProtocol` is a top-level string that tells your endpoint which protocol the wallet engaged the issuer over. Currently the only supported value is `openid4vci`, covering both the [Authorization Code](/docs/issuance/authorization-code/overview) and [Pre-authorized Code](/docs/issuance/pre-authorized-code/overview) flows. [Contact us](mailto:dev-support@mattr.global) if you need support for additional issuance protocols. Map this parameter when the same credential configuration is offered through multiple protocols and your endpoint needs to vary the returned data based on the channel, for example applying protocol-specific business rules. ```json title="Branch by issuance protocol" { "requestParameters": { "holderId": { "mapFrom": "claims.sub" }, "protocol": { "mapFrom": "issuanceProtocol" } } } ``` #### Static parameters [#static-parameters] A parameter with only a `defaultValue` is sent on every request, with no mapping. Use it for values that are constant for this claims source but that the endpoint still needs (API version, tenant code, fixed feature flag). ```json title="Pin an API version" { "requestParameters": { "apiVersion": { "defaultValue": "2024-08" }, "subjectId": { "mapFrom": "claims.sub" } } } ``` A `defaultValue` can also be an object or an array, which is useful when the endpoint accepts structured filters in a POST body: ```json title="Static structured value" { "requestMethod": "POST", "requestParameters": { "filters": { "defaultValue": { "includeArchived": false, "regions": ["AU", "NZ"] } }, "subjectId": { "mapFrom": "claims.sub" } } } ``` #### Combine sources [#combine-sources] You can mix mappings from different sources in a single configuration. The example below identifies the holder from the authentication provider, scopes the query to the credential type, tags the call with the wallet instance and protocol, and pins an API version: ```json title="Combined parameters" { "requestMethod": "POST", "requestParameters": { "subjectId": { "mapFrom": "authenticationProvider.subjectId" }, "credentialType": { "mapFrom": "credentialConfiguration.type" }, "walletInstanceId": { "mapFrom": "wallet.instanceId" }, "protocol": { "mapFrom": "issuanceProtocol" }, "apiVersion": { "defaultValue": "2024-08" } } } ``` ### Error-proof your queries with default values [#error-proof-your-queries-with-default-values] Adding a `defaultValue` alongside `mapFrom` keeps issuance running when an expected claim is missing or evaluates to `undefined`. Without a fallback, the parameter is simply omitted from the request, which often causes the endpoint to return a 404 or an empty body, both of which can result in [issuance failures](/docs/issuance/claims-source/overview#failures-and-errors) depending on how your credential configuration is set up. Two patterns are worth knowing. #### Fallback to a safe value [#fallback-to-a-safe-value] When the mapped claim is optional, supply a `defaultValue` that your endpoint can interpret without error. Be careful that the fallback represents the right outcome: for a lookup key, the fallback should match a record your endpoint knows how to handle (often a "no match" placeholder), not a real user. ```json title="Fallback for an optional segment" { "requestParameters": { "subjectId": { "mapFrom": "claims.sub" }, "segment": { "mapFrom": "claims.customer_segment", "defaultValue": "standard" } } } ``` Here, every holder gets a `segment` value. If the authentication provider did not return one, the endpoint receives `standard` and can apply its default rules. #### Fallback to a structured value [#fallback-to-a-structured-value] `defaultValue` accepts strings, arrays, and objects, which is helpful when the endpoint expects a fixed shape: ```json title="Structured fallback" { "requestMethod": "POST", "requestParameters": { "permissions": { "mapFrom": "claims.permissions", "defaultValue": [] }, "preferences": { "mapFrom": "claims.preferences", "defaultValue": { "locale": "en-US", "format": "standard" } } } } ``` #### What a fallback cannot do [#what-a-fallback-cannot-do] A `defaultValue` only fires when the mapping fails or resolves to `undefined`. It does not run when: * The claims source returns an error (non-2XX status). * The claims source returns an empty body or a body missing the claim you expected. * The claims source times out (response longer than three seconds). For those cases, the lever is in the [Credential configuration](/docs/issuance/credential-configuration/overview#claims-mapping) itself: set a `defaultValue` on optional claim mappings so issuance can complete even if the claims source returns nothing useful. The two layers compose: the claims source `defaultValue` keeps the outbound request well-formed, and the credential configuration `defaultValue` keeps the outbound credential well-formed. Never use `defaultValue` to mask an authorization problem. If your endpoint can return data for an unauthenticated or unidentified caller (for example because the lookup key fell back to a static value), the wrong holder may receive someone else's claims. Fall back to safe metadata, not to identifiers. ## Best practices [#best-practices] * **Reuse existing access control.** If your data system already lives behind OAuth, use `oauth-client-credentials`; if it lives behind an API gateway with shared keys, use `api-key`. Do not introduce a parallel auth scheme just for the claims source. * **Keep parameter names aligned with the endpoint.** The keys in `requestParameters` are the contract with your endpoint. Treat changes as a coordinated deployment, not a configuration tweak. * **Test with a representative holder.** MATTR VII does not validate authorization values, parameter paths, or the endpoint contract at save time. A test issuance is the only way to confirm the integration works end to end. * **Stay inside the three-second budget.** The endpoint must respond within three seconds or the issuance fails. Cache, pre-compute, or move heavy work behind asynchronous processing rather than blocking the claims request. * **Return `{}` when no claims are found.** An empty JSON object lets issuance proceed using credential configuration defaults. Returning a 404 fails the issuance. * **Audit fallbacks.** Review every `defaultValue` against whether it could mask a real lookup failure for a real holder. Fallbacks are safest for non-identifying metadata. ## What's next? [#whats-next] * Walk through a complete claims source integration in the [Claims source tutorial](/docs/issuance/claims-source/tutorial). * Review the full request and response schemas in the [Claims source API reference](/docs/issuance/claims-source/api-reference). * Learn how returned claims are mapped into the issued credential in the [Credential configuration overview](/docs/issuance/credential-configuration/overview#claims-mapping). # Claims source URL: /docs/issuance/claims-source/overview ## Overview [#overview] Issuers databases are not always integrated with their Authentication providers and might require a separate integration into the issuance workflow. Claims sources allow configuring your tenant to use a compatible endpoint to fetch claims and use them in issued credentials. Your Claims source configuration includes your [authorization](#authorization) credentials for that service, as well as [request parameters](#request-parameters) that can be used to query it when fetching data. Once configured, MATTR VII will make an API request to the claims source using the configured request parameters and fetch available data as part of the issuance workflow. Any retrieved data from the claims source can be used in the issued credential. Claims sources must be explicitly referenced by a [credential configuration](/docs/issuance/credential-configuration/overview) included in the credential offer to be used as part of either an OID4VCI [Authorization Code](/docs/issuance/authorization-code/overview) or [Pre-authorized Code](/docs/issuance/pre-authorized-code/overview) issuance flows. ## Requirements [#requirements] Each configured claims source must meet the following guidelines: * Must be accessible from the internet via HTTPS (e.g. `https://example.com`). * Must expose an endpoint supporting HTTPS request using either GET or POST. This endpoint is what MATTR VII will call when querying the claims source. * MATTR VII connects to claims sources using IPv4 only. All requests explicitly use IPv4 to avoid connection failures with unsupported IPv6 traffic. ## Authorization [#authorization] MATTR VII supports two authorization methods to interact with your protected claim sources: * `api-key` : The provided key is passed as a `x-api-key` header in all requests made to the claims source. * `oauth-client-credentials` : MATTR VII will first use your oauth client credentials to request an access token. Once the token is retrieved, it is used as an `Authorization` header prefixed by the token type in all requests made to the claims source. MATTR VII only supports `Bearer` access tokens. ## Request parameters [#request-parameters] Credentials are likely to only require a subset of the data available on your claims source. To accommodate this, MATTR VII can pass request parameters that can be used to filter data queries. These parameters can be: * Any claims retrieved from the [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) and/or the [Interaction hook](/docs/issuance/authorization-code/interaction-hook/overview) components in an [Authorization Code flow](/docs/issuance/authorization-code/overview). * Any claims provided as part of the Credential offer in a [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview). * Either dynamic or static. ### Dynamic request parameters [#dynamic-request-parameters] You can map dynamic request parameters from different objects that are available in the OID4VCI workflow: * `claims` : This object includes all the claims retrieved from the Authentication provider and/or the interaction hook. The path to a specific claim inside the claims object depends on the configuration of the component the claim was retrieved from (Authentication provider/interaction hook). * `authenticationProvider` : You can use this object to pass elements retrieved from the authentication provider as request parameters: * `url` : URL of the authentication provider. * `subjectId` : Unique identifier of the user in the authentication provider. * `providerId` : Unique identifier of the authentication provider. * `credentialConfiguration` : You can use this object to pass elements of the credential configuration as request parameters: * `id` : Unique configuration identifier. * `type` : Credential type, globally unique per Credential configuration on your tenant (configuration type is defined as part of their setup process). * `profile` : Credential format (configuration profile is defined as part of their setup process). The following options are available: * mDocs: `mobile`. * CWT credentials: `compact`. * Semantic CWT credentials: `compact-semantic`. * `wallet` : When [Wallet Attestation](/docs/issuance/credential-issuance/wallet-attestation) is enabled, you can use this object to pass the following information as request parameters: * `id` : Unique identifier of the wallet. In the Attestation JWT, this is provided as `sub`. Related documentation may also refer to `client_id` in the JWT access token, but `sub` is the claim used to identify the wallet application in the attestation. * `instanceId` : Unique identifier of the wallet instance, as provided in the attestation JWT. This enables instance-specific data retrieval from the claims source. * `issuanceProtocol` : Identifies which protocol is used to issue the credential. This allows your claims source to return different data sets based on the issuance protocol in use. The following values are available: * `openid4vci` : OpenID for Verifiable Credential Issuance flows (Authorization Code and Pre-authorized Code). * [Contact us](mailto:dev-support@mattr.global) for additional protocol support. Using dynamic request parameters from the `credentialConfiguration` object relies on structuring your database query endpoints in a way that can make sense of the `id`, `type` and `profile` parameters. The `client` object (exposing `client.instanceId`) is still available for backwards compatibility but is being phased out. Update existing claims source configurations to map from `wallet.instanceId` instead. To support scenarios where mapping fails or evaluated to `undefined` (attribute doesn’t exist) you can also provide a default value to use as a fallback parameter. This can be a string, an object or an array. ### Static request parameters [#static-request-parameters] You can define a static request parameter by only providing a default value with no mapping path. Doing so means the default value is always passed as a request parameter, regardless of what claims are available in a specific issuance workflow. ## Response criteria [#response-criteria] When a claims source is queried, MATTR VII expects a response that includes claims to be used in the issued credential. Issuance would fail if any of the following criteria are not met: * Return a response within three seconds to avoid request timeout. * Return a 2XX HTTP status code when the request was successful. * Response body must be a valid JSON, denoted by the application/json MIME type. * Each key in the JSON object must be a claim name and each value must be a claim value. * If no claims are found, an empty object must be returned in the response body. It is recommended to respond with error codes only when something exceptional happens, to avoid [issuance failures](#failures-and-errors). ## Failures and errors [#failures-and-errors] A claims source integration will result in issuance failures in the following situations: * Whenever the claims source does not return a 2XX (success) status with a [valid](#response-criteria) response body. Common causes include: * Network or DNS connection errors. * Authentication or authorization failures. * Request timeouts (no response within three seconds). * Not returning an empty JSON object when no claims are found. * Any other condition that prevents a successful 2XX response. * When information returned from the claims source does not satisfy an explicit [credential configuration](/docs/issuance/credential-configuration/overview) requirement (e.g., when no value for a required claim is available and no `defaultValue` is set in the credential configuration). Refer to [claim mapping](/docs/issuance/credential-configuration/overview#claims-mapping) for more information. # Learn how to integrate a claims source into an OID4VCI workflow URL: /docs/issuance/claims-source/tutorial ## Introduction [#introduction] In an [OID4VCI issuance workflow](/docs/issuance/oid4vci-overview), you can enhance the credential issuance process by configuring your tenant to fetch claims directly from a compatible [Claims source](/docs/issuance/claims-source/overview). These claims can then be used to issue verifiable credentials tailored to the user's data. Claim sources can be integrated both to OID4VCI [Authorization Code](/docs/issuance/authorization-code/overview) and [Pre-authorized Code](/docs/issuance/pre-authorized-code/overview) flows. This tutorial walks you through setting up an [OID4VCI workflow](/docs/issuance/oid4vci-overview) that issues an ISO-compliant mobile Driving Licence (mDL) by integrating a claims source to retrieve the holder's address and driving privilege information at issuance time. The holder's driver's licence number is used as the query parameter to look up their record in the claims source—mirroring how a real-world issuer would query a DMV or licensing authority database. The tutorial shows you how to achieve this using both the MATTR Portal and the MATTR VII Platform API. ## Prerequisites [#prerequisites] * Ensure you have completed either the [Authorization Code](/docs/issuance/authorization-code/tutorial) or [Pre-authorized Code](/docs/issuance/pre-authorized-code/tutorial) tutorials, as the claims source tutorial builds upon them. * Download the sample [Claims source application](https://github.com/mattrglobal/sample-apps/tree/master/claims-source-app), which will be used to simulate a claims source. * To test locally you will need to expose your local claims source to the internet. You can do this by setting up a [free ngrok account](https://ngrok.com/), using a Cloudflare tunnel or using your own solution. For this tutorial we will be using ngrok. Sign up for a free account at [ngrok.com](https://ngrok.com/). We recommend using the MATTR VII [Postman collection](/docs/api-reference#postman-collection) in this tutorial. While this isn't an explicit prerequisite it can really speed things up. ## Tutorial overview [#tutorial-overview] The current tutorial builds upon the [Authorization Code](/docs/issuance/authorization-code/tutorial) or [Pre-authorized Code](/docs/issuance/pre-authorized-code/tutorial) tutorials by adding the following steps: 1. **Set up a local Claims source:** Use the provided sample Node.js application to simulate a claims source by providing a REST API to retrieve claims for specific users. 2. **Integrate the Claims source with MATTR VII:** Configure MATTR VII to connect to and utilize the sample Claims source for retrieving claims during an OID4VCI workflow. The following diagram illustrates how a claims source fits into the OID4VCI issuance workflow: The workflow proceeds as follows: 1. **Initiate issuance:** The wallet triggers an OID4VCI issuance workflow — either an [Authorization Code](/docs/issuance/authorization-code/overview) or [Pre-authorized Code](/docs/issuance/pre-authorized-code/overview) flow. During this stage, claims may be gathered from an Authentication Provider and/or an Interaction Hook. 2. **Resolve claims source:** MATTR VII reads the [credential configuration](/docs/issuance/credential-configuration/overview) referenced in the credential offer and identifies the configured claims source. 3. **Authorize the request:** MATTR VII authenticates with the claims source using the configured authorization method: * **OAuth Client Credentials** — MATTR VII requests a Bearer access token from the authorization server and uses it as an `Authorization` header. * **API Key** — MATTR VII passes the configured key as an `x-api-key` header. 4. **Fetch claims:** MATTR VII sends an HTTP request (GET or POST) to the claims source endpoint, including any configured [request parameters](/docs/issuance/claims-source/overview#request-parameters) — these can be dynamic (mapped from available claims or credential configuration data) or static. 5. **Return claims:** The claims source must respond within three seconds with a `2XX` status code and a valid JSON object containing the claims to be used in the credential. An empty object is returned if no claims are found. 6. **Map and issue:** MATTR VII maps the returned claims according to the credential configuration and generates the signed credential, which is delivered to the wallet. ## Tutorial steps [#tutorial-steps] ### Set up a local Claims source [#set-up-a-local-claims-source] 1. Clone the [MATTR Sample Apps repo](https://github.com/mattrglobal/sample-apps) to your machine and navigate to the `claims-source-app` folder. 2. Rename the `env-example` file to `.env`. 3. Change the value of `NGROK_AUTHTOKEN` to your ngrok authentication token. 4. Open the `database.json` file and add a new object to represent a user with their mDL record: * Set the `licenseNumber` value (for example, `DL-123456`). This is the driver's licence number that will be used as the query parameter when looking up the user's record in the claims source. * If you are building on top of the [Authorization Code flow tutorial](/docs/issuance/authorization-code/tutorial), the same value must exist as a property for the authenticated user in your Auth0 application. * If you are building on top of the [Pre-authorized Code flow tutorial](/docs/issuance/pre-authorized-code/tutorial), you can use any value, but make note of it as you will need it later when you create the credential offer. * Populate the following mDL address fields with realistic test values: * `resident_address` — street address (for example, `123 Main Street`) * `resident_city` — city (for example, `Springfield`) * `resident_state` — state or region code (for example, `IL`) * `resident_postal_code` — postal code (for example, `62701`) 5. Start the claims source app either via npm or docker: ```bash title="Start via npm" npm install npm run dev ``` or ```bash title="Start via docker" docker compose up --build ``` 6. Make note of the `Public Claims Source URL` displayed in the terminal after starting the app. This URL will be used to configure the Claims source in your MATTR VII tenant. Your claims source is now set up and ready to be used. The provided application meets the [requirements](/docs/issuance/claims-source/overview#requirements) for integrating a claims source into a MATTR VII OID4VCI workflow, and will return the holder's resident address details when queried with their driver's licence number. Next, we will configure MATTR VII to connect to this claims source. ### Configure the Claims source in your MATTR VII tenant [#configure-the-claims-source-in-your-mattr-vii-tenant] This step can be achieved either via the MATTR Portal or the MATTR VII Platform APIs. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Claims sources**. 3. Click the **Create new** button. 4. Insert a meaningful name for your Claims source in the *Name* field. For example, "DMV Claims source". 5. In the *URL* field, paste the `Public Claims Source URL` generated when you started the claims source application. 6. In the *Authorization type* section, select **API Key** as the type. 7. In the *API Key* field, paste `supersecretapikey`. This is the API key used by the sample claims source application to control access. In Production environments, it is strongly recommended to use a more secure API key. 8. Paste the following code into the *Request parameters* field: ```json title="Request parameters" { "licenseNumber": { "mapFrom": "claims.licenseNumber" } } ``` This maps the holder's driver's licence number to a query parameter that will be sent to the claims source to look up their mDL record. * In Authorization code flows the `licenseNumber` claim must be provided by the Authentication provider. It can be retrieved from the authenticated user's identity in an Interaction hook and added to the claim set that is returned to MATTR VII, or it can be included in the ID token claims by the Authentication provider (e.g., Auth0) and mapped in the credential configuration. * In Pre-authorized code flows it is provided by the issuer as part of the credential offer. In this case, you would map it from the credential offer claims in the same way as shown above. 9. Select **Create** to create the Claims source. Make a request of the following structure to [configure a new claims source](/docs/issuance/claims-source/api-reference#configure-a-claims-source): ```http title="Request" POST /v1/claim-sources ``` ```json title="Request body" { "name": "DMV Claims source", "url": "", // [!code highlight] "authorization": { // [!code highlight] "type": "api-key", // [!code highlight] "value": "supersecretapikey" // [!code highlight] }, "requestMethod": "GET", "requestParameters": { // [!code highlight] "licenseNumber": { // [!code highlight] "mapFrom": "claims.licenseNumber" // [!code highlight] } // [!code highlight] } } ``` * `url` : Replace this with the `Public Claims Source URL` generated when you started the claims source application. The URL should follow this format: `https:///claims`. * `authorization` : Specifies how to access the claims source. The sample claims source application uses an API key (`supersecretapikey`) for access control. For production environments, it is strongly recommended to use a more secure API key. * `requestParameters` : Maps the holder's driver's licence number to a `licenseNumber` query parameter. MATTR VII sends this parameter to the claims source to look up the holder's mDL record, returning their resident address and other mDL fields. * In Authorization code flows the `licenseNumber` claim must be provided by the Authentication provider. It can be retrieved from the authenticated user's identity in an Interaction hook and added to the claim set that is returned to MATTR VII, or it can be included in the ID token claims by the Authentication provider (e.g., Auth0) and mapped in the credential configuration. * In Pre-authorized code flows it is provided by the issuer as part of the credential offer. In this case, you would map it from the credential offer claims in the same way as shown above. **Response** ```json title="Response body" { "id": "945214ad-3635-4aff-b51d-61d69a3c8eee" // [!code focus] // Remaining response properties } ``` * `id` : Unique identifier for the configured Claims source. We will use it in the next step to integrate the Claims source into the OID4VCI workflow. ### Use the Claim source in a credential configuration [#use-the-claim-source-in-a-credential-configuration] This step can be achieved either via the MATTR Portal or the MATTR VII Platform APIs. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **mDocs**. 3. Select the **Create new** button. 4. In the *Name* text box, enter a clear and descriptive title that will appear on the credential in the wallet, for example "Mobile Driving Licence". 5. In the *Description* text box, enter a clear and descriptive description that will appear on the credential in the wallet, for example "ISO 18013-5 compliant mDL". 6. In the *Credential type* text box, enter `org.iso.18013.5.1.mDL`. 7. Paste the following JSON into the *Claim mappings* text box: ```json title="Claim mappings" { "org.iso.18013.5.1": { "family_name": { "mapFrom": "claims.family_name", "type": "string" }, "given_name": { "mapFrom": "claims.given_name", "type": "string" }, "document_number": { "mapFrom": "claims.licenseNumber", "type": "string" }, "resident_address": { "mapFrom": "claims.resident_address", "type": "string" }, "resident_city": { "mapFrom": "claims.resident_city", "type": "string" }, "resident_state": { "mapFrom": "claims.resident_state", "type": "string" }, "resident_postal_code": { "mapFrom": "claims.resident_postal_code", "type": "string" } } } ``` The address claims (`resident_address`, `resident_city`, `resident_state`, `resident_postal_code`) will be retrieved from the Claims source configured in the previous step. The identity claims (`family_name`, `given_name`) will be retrieved from the authentication provider (e.g., Auth0), and `document_number` is mapped from the `licenseNumber` claim. Note that a compliant mDL credential has specific requirements for the claims it includes. In this tutorial we are including the `resident_address`, `resident_city`, `resident_state` and `resident_postal_code` claims from the Claims source, and the `family_name`, `given_name` and `document_number` claims from the authentication provider, to demonstrate how to map claims from multiple sources to meet these requirements. 8. Use the *Claim source* dropdown to select the Claims source you created in the previous step. This will allow the credential to retrieve claims from the configured Claims source during issuance. 9. Enter "1" in the *Months* text box in the *Validity for* panel to set the credential expiration period. 10. Select the **Create** button to create the credential configuration. Make the following request to create an ISO 18013-5 compliant [mDoc credentials configuration](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration) that includes the holder's identity claims from the authentication provider, and their resident address retrieved from your configured Claims source: ```http title="Request" POST /v2/credentials/mobile/configurations ``` ```json title="Request body" { "type": "org.iso.18013.5.1.mDL", "expiresIn": { "months": 1 }, "claimMappings": { "org.iso.18013.5.1": { "family_name": { "mapFrom": "claims.family_name", "type": "string" }, "given_name": { "mapFrom": "claims.given_name", "type": "string" }, "document_number": { "mapFrom": "claims.licenseNumber", "type": "string" }, "resident_address": { // [!code highlight] "mapFrom": "claims.resident_address", // [!code highlight] "type": "string" // [!code highlight] }, // [!code highlight] "resident_city": { // [!code highlight] "mapFrom": "claims.resident_city", // [!code highlight] "type": "string" // [!code highlight] }, // [!code highlight] "resident_state": { // [!code highlight] "mapFrom": "claims.resident_state", // [!code highlight] "type": "string" // [!code highlight] }, // [!code highlight] "resident_postal_code": { // [!code highlight] "mapFrom": "claims.resident_postal_code", // [!code highlight] "type": "string" // [!code highlight] } // [!code highlight] } }, "branding": { "name": "Mobile Driving Licence", "description": "ISO 18013-5 compliant mDL", "backgroundColor": "#1a3a5cff" }, "claimSourceId": "", // [!code highlight] "includeStatus": true } ``` * `resident_address`, `resident_city`, `resident_state`, `resident_postal_code` : These claims represent the holder's residential address. They are retrieved from the Claims source using the holder's `licenseNumber` as the lookup key, and stored in the `claims` object. During credential issuance they are mapped into the `org.iso.18013.5.1` namespace of the mDL. Note that a compliant mDL credential has specific requirements for the claims it includes. In this tutorial we are including the `resident_address`, `resident_city`, `resident_state` and `resident_postal_code` claims from the Claims source, and the `family_name`, `given_name` and `document_number` claims from the authentication provider, to demonstrate how to map claims from multiple sources to meet these requirements. * `claimSourceId` : Replace this with the `id` value returned in the response when you configured the Claims source in the previous step. *Response* ```json title="Response body" { "id": "294868aa-3814-4a50-9862-5ff48381a8e5" // [!code focus] //... rest of your credential configuration } ``` * `id` : Unique identifier for the created mDocs credentials configuration. This ID is used to create a Credential offer in the next step. ### Create a Credential offer [#create-a-credential-offer] You now have all the pieces in place and can wrap them all together to generate a [Credential offer](/docs/issuance/credential-offer/overview) and share it with the intended holder. Make sure you create a credential offer that matches the flow you are implementing. Creating pre-authorized code flow credential offers in the MATTR Portal is for testing purposes only. The populated user ID is generated by the MATTR Portal and cannot be changed to another user later. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Credential offer**. 3. Use the *Workflow* radio button to select **Pre-authorized code flow**. 4. Select the **Select** button. 5. Check the checkbox next to the credential configuration you created in the previous step. 6. Select the **Apply** button. 7. In the Claims panel, select **Add new** and add a claim with the following details: * Attribute: `licenseNumber` * Type: `string` * Value: The driver's licence number you used when you [set up your local claims source](#set-up-a-local-claims-source) (for example, `DL-123456`). 8. Select the **Generate** button. 9. Copy the Transaction code that is displayed on the screen.\ In production deployments you will need to provide it to the user through a different channel (e.g., email, SMS). 10. Download the displayed QR code.\ This QR code will be used by the holder to claim the credential. Make the following request to [generate a new Pre-authorized Code flow Credential offer](/docs/issuance/pre-authorized-code/api-reference#create-credential-offer): ```http title="Request" POST /v1/openid/offers/pre-authorized ``` ```json title="Request body" { "credentials": ["707e920a-f342-443b-ae24-6946b7b5033e"], // [!code highlight] "transactionCodeConfiguration": { "inputMode": "numeric", "description": "Please enter the one-time code that was sent to you." }, "claims": { "family_name": "Doe", "given_name": "Jane", "licenseNumber": "" // [!code highlight] }, "expiresIn": { "minutes": 10 } } ``` * `credentials` : Populate the array with the `id` element returned in the response when you created an mDocs credentials configuration in the previous step. * `licenseNumber` : Replace `` with the driver's licence number you used when you [set up your local claims source](#set-up-a-local-claims-source) (for example, `DL-123456`). MATTR VII will use this value to query the claims source and retrieve the holder's resident address. In this example we are manually inserting the driver's licence number into the credential offer request. In a real-world scenario, you would want to dynamically populate this value based on the authenticated user's identity, for example by retrieving it from your identity store. *Response* ```json title="Response body" { "id": "e6e5e43c-8053-464a-aca4-ca43da765c97", "uri": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Flearn.vii.au01.mattr.global%22%2C%22credentials%22%3A%5B%22b7b380be-1467-446b-8683-1d131e6532be%22%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%229K3N9UPQD-Hs5f5-gPx0fRJEmb1XTZsWszzXL024pV0%22%2C%22tx_code%22%3A%7B%22length%22%3A6%2C%22input_mode%22%3A%22numeric%22%2C%22description%22%3A%22Please%20enter%20the%20one-time%20code%20that%20was%20sent%20to%20you%20via%20email.%22%7D%7D%7D%7D", // [!code focus] "userId": "6e30dd69-c867-4279-afd3-e6619253b4a4", "expiresAt": "2025-08-25T01:39:00.523Z", "transactionCode": "763677" // [!code focus] } ``` You will need to obtain two important elements from the response: * `transactionCode` : This is the one-time code that the user will need to provide in order to claim the credential. In production deployments you will need to provide it to the user through a different channel (e.g., email, SMS). * `uri` : This URI can be used by a digital wallet to trigger the OID4VCI workflow. Use one of the following tools to convert the `uri` value to a QR code (make sure you use the `Plain text` option where available) and save the generated QR code as an image file. * [https://www.the-qrcode-generator.com/](https://www.the-qrcode-generator.com/) * [http://goqr.me/api/](http://goqr.me/api/) * [https://www.qr-code-generator.com/](https://www.qr-code-generator.com/) MATTR is not affiliated with any of these service providers and cannot vouch for their offerings. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Credential offer**. 3. Select the **Select** button. 4. Check the checkbox next to the credential configuration you created in the previous step. 5. Select the **Apply** button. 6. Select the **Generate** button. 7. Download the generated QR code by selecting the **Download** button. Make the following request to [generate a new Credential offer](/docs/issuance/authorization-code/api-reference#create-credential-offer): ```http title="Request" POST /v1/openid/offers ``` ```json title="Request body" { "credentials": [""] } ``` * `credentials`: Replace this with the `id` value returned in the response when you created the mDocs credentials configuration in the previous step. *Response* The response will include a `uri` element which can be used by a digital wallet to trigger the OID4VCI workflow. Use one of the following tools to convert the `uri` value to a QR code (make sure you use the `Plain text` option where available): * [https://www.the-qrcode-generator.com/](https://www.the-qrcode-generator.com/) * [http://goqr.me/api/](http://goqr.me/api/) * [https://www.qr-code-generator.com/](https://www.qr-code-generator.com/) MATTR is not affiliated with any of these service providers and cannot vouch for their offerings. Save the generated QR code on your computer. ### Test the workflow [#test-the-workflow] 1. Open the GO hold example app. 2. Select **Scan**. 3. Scan the QR code generated in the previous step. 4. Review the credential offer and select **Accept**. 5. Follow the issuance workflow instructions to claim the credential. You should now see an mDL in your wallet that includes the holder's resident address (`resident_address`, `resident_city`, `resident_state`, `resident_postal_code`), which was retrieved from your configured Claims source. MATTR VII obtained these claims by querying the Claims source using the holder's driver's licence number as a query parameter—either supplied by the authentication provider (Authorization Code flow) or specified in the credential offer (Pre-authorized Code flow). ## What's next? [#whats-next] Check out more resources on MATTR Learn that will enable you to: * Configure an [Interaction hook](/docs/issuance/authorization-code/interaction-hook/tutorial) to redirect the user to custom components as part of the issuance workflow (only supported for the [Authorization Code flow](/docs/issuance/authorization-code/overview)). * Apply branding to issued credentials as part of creating a [Credential configuration](/docs/issuance/credential-configuration/overview). # Create a Credential configuration URL: /docs/issuance/credential-configuration/guide ## Introduction [#introduction] In an [OID4VCI](/docs/issuance/oid4vci-overview) issuance workflow, [Credential offers](/docs/issuance/credential-offer/overview) are created based on [Credential configurations](/docs/issuance/credential-configuration/overview). These are templates that define rules and parameters that control the structure and content of the credentials being issued. The purpose of this tutorial is to demonstrate how to create a Credential configuration using the MATTR Portal or via API calls. ## Prerequisites [#prerequisites] * Ensure you have completed either the [Authorization Code](/docs/issuance/authorization-code/tutorial) or [Pre-authorized Code](/docs/issuance/pre-authorized-code/tutorial) tutorials, as the claims source tutorial builds upon them. * Review the [Credential configuration overview](/docs/issuance/credential-configuration/overview) page for a detailed explanation of the key concepts and considerations involved in creating a credential configuration. We recommend using the MATTR VII [Postman collection](/docs/api-reference#postman-collection) in this tutorial. While this isn't an explicit prerequisite it can really speed things up. ## Guide overview [#guide-overview] When planning any credential configuration, an issuer should take the following factors into account: * **Credential type:** Used to uniquely identify credentials issued via this configuration. * **Credential content:** Decide what information will be included in the credential, and how it will be structured. * **Credential validity:** Set the appropriate validity period to reflect how long the credential should be considered accurate and trustworthy. * **Revocation support:** Determine whether the credential needs to support revocation status updates. * **Branding configuration:** Specify how the credential should appear in a digital wallet. While display conventions are not yet standardized, this tutorial uses MATTR’s proprietary format to support branding, clarity, and holder trust. This tutorial will walk you through each of these steps and explain the different considerations, options, and best practices to help you create a well-formed credential configuration. ### Credential type [#credential-type] The credential type is used to differentiate between different types of credentials. It is a unique identifier that specifies the purpose and structure of the credential being issued. It is set using the [`type`](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=type\&t=request) property when creating a credential configuration: ```json title="Setting Credential Type" { "type": "org.iso.18013.5.1.mDL" // Rest of the credential configuration } ``` Refer to the [Credential configuration overview](/docs/issuance/credential-configuration/overview#credential-type) for considerations and best practices on defining credential types. ### Credential content [#credential-content] The credential content defines the specific information that will be included in the credential. This can include various claims about the holder, such as their name, email address, and any other relevant data. The content is set using the claimMappings object, as shown in the following example: ```json title="Setting Credential Content" { "claimMappings": { "com.example.personaldetails.1": { "name": { "mapFrom": "claims.name", "type": "string", "required": true }, "email": { "mapFrom": "claims.email", "type": "string", "defaultValue": "Not provided" } } }, "claimSourceId": "78e1b90c-401d-45bb-89c0-938da4d44c60" // Rest of the credential configuration } ``` This example maps claims into a single namespace (`com.example.personaldetails.1`) and defines two claims within it: * `name` : This claim is mapped from the `claims.name` property in the claim source. It is of type `string` and is marked as `required`, meaning that the issuance process will fail if this claim is not provided. * `email` : This claim is also mapped from the `claims.email` property in the claim source. It is of type `string` but is not marked as required. If the claim is not provided, it will default to the value "Not provided". The `claimSourceId` property specifies the [claims source](/docs/issuance/claims-source/overview) from which claims are dynamically retrieved as part of the issuance flow. Each claim can also include an optional `display` array to provide localized labels that wallets can render in the holder's preferred language. For example, to label the `name` claim above in both English and German: ```json title="Claim with localized display labels" { "name": { "mapFrom": "claims.name", "type": "string", "required": true, "display": [ { "name": "Full Name", "locale": "en-US" }, { "name": "Vollständiger Name", "locale": "de-DE" } ] } } ``` When configured, these labels are surfaced in the issuer metadata at `/.well-known/openid-credential-issuer`. `locale` values must be unique within a given claim's `display` array, and `display` is currently supported for mDoc credential configurations only. Refer to the [Credential configuration overview](/docs/issuance/credential-configuration/overview#credential-content) for considerations and best practices on defining and mapping credential content, including [localized display labels](/docs/issuance/credential-configuration/overview#localized-display-labels). ### Credential validity [#credential-validity] Credential validity defines the period during which the credential is considered valid and can be used for its intended purpose. This is set using a combination of three properties in the credential configuration. Examples include: ```json { "expiresIn": { "days": 30 } // Rest of the credential configuration } ``` In this example we are setting the credential to expire 30 days after issuance. ```json { "validFrom": { "defaultValue": "2023-01-01T00:00:00Z" }, "validUntil": { "defaultValue": "2023-02-01T23:59:59Z" } // Rest of the credential configuration } ``` Note that in this example `expiresIn` must also be set, as it is a required property. Its value will be ignored as both `validFrom` and `validUntil` are provided. Refer to the [Credential configuration overview](/docs/issuance/credential-configuration/overview#credential-validity) for considerations and best practices on defining and mapping credential validity. ### Revocation support [#revocation-support] MATTR VII allows you to update the status of issued credentials, enabling you to [revoke](/docs/issuance/revocation/overview) or suspend credentials when necessary. Verifiers can check the status of a presented credential without compromising the holder’s privacy. Support for credential revocation is configured as part of the credential configuration, using the [`includeStatus`](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=includeStatus\&t=request) boolean: ```json title="Revocation Configuration" { "includeStatus": true // Rest of credential configuration } ``` * `includeStatus` : When set to `true`, issued credentials are revocable. They'll include a `status` object, which refers a [Status list](/docs/issuance/revocation/overview#status-list) where the credential status is indicated. ### Credential branding [#credential-branding] Credential configurations can include information regarding how to brand them when they are displayed in a digital wallet. This is useful for enhancing the user experience and ensuring that the credential is easily recognizable. While display conventions are not yet standardized, this tutorial uses MATTR’s proprietary format to support branding, clarity, and holder trust. Branding is controlled via the following properties of the [`branding`](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=branding\&t=request) object: ```json title="Branding Configuration" { "branding": { "name": "Course credential", "description": "Diploma in Management", "backgroundColor": "#860012", "watermarkImage": "https://example-path-to-image-data.com", "issuerLogo": "data:image/png;base64,anything", "issuerIcon": "data:image/svg+xml;base64,anything" } // Rest of credential configuration } ``` Credential branding explainer * `name` : Displayed on the top part of the credential. * `description` : Displayed below the name field. * `backgroundColor` : Applied as the credential background color. * `watermarkImage` : Displayed as a pattern on top of the credential. * `issuerLogo` : Displayed on the bottom part of the credential. * `issuerIcon` : Displayed next to the issuer’s name. Refer to the [API Reference](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=branding\&t=request) for requirements and best practices for each of these elements. ### Putting it all together [#putting-it-all-together] **Step 1: Create a Credential configuration** 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **mDocs**. 3. Select the **Create new** button. 4. In the *Name* text box, enter a clear and descriptive title that will appear on the credential in the wallet, for example "My Credential Configuration Tutorial Credential". 5. In the *Description* text box, enter a clear and descriptive description that will appear on the credential in the wallet, for example "Credential Configuration Best Practices". 6. In the *Credential type* text box, enter a unique identifier for the credential type, for example `com.example.credentialconfiguration.2`. 7. Copy and paste the following JSON into the *Claim mappings* text box: ```json title="Claim mappings" { "com.example.personaldetails.1": { "name": { "mapFrom": "claims.name", "type": "string", "required": true }, "email": { "mapFrom": "claims.email", "type": "string", "required": true }, "age": { "mapFrom": "claims.age", "type": "number" } } } ``` 8. Enter "1" in the *Months* text box in the *Validity for* panel to set the credential expiration period. 9. Select the **Create** button to create the credential configuration. **Step 2: Create a Credential offer** You can now generate a [Credential offer](/docs/issuance/credential-offer/overview) and share it with the intended holder. Make sure you create a credential offer that matches the flow you are implementing. Creating pre-authorized code flow credential offers in the MATTR Portal is for testing purposes only. The populated user ID is generated by the MATTR Portal and cannot be changed to another user later. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Credential offer**. 3. Use the *Workflow* radio button to select **Pre-authorized code flow**. 4. Select the **Select** button. 5. Check the checkbox next to the credential configuration you created in the previous step. 6. Select the **Apply** button. 7. In the Claims panel, select **Add new** and add the following claims: * Attribute: `name` * Type: `string` * Value: You can use any value here. It will be mapped to the `name` claim in the issued credential. * Attribute: `email` * Type: `string` * Value: You can use any value here. It will be mapped to the `email` claim in the issued credential, such as the email used when you created the claim source (if applicable) or any email you want if you are not using a claim source. 8. Select the **Generate** button. 9. Copy the Transaction code that is displayed on the screen.\ In production deployments you will need to provide it to the user through a different channel (e.g., email, SMS). 10. Download the displayed QR code.\ This QR code will be used by the holder to claim the credential. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Credential offer**. 3. Use the *Workflow* radio button to select **Authorization code flow**. 4. Select the **Select** button. 5. Check the checkbox next to the credential configuration you created in the previous step. 6. Select the **Apply** button. 7. Select the **Generate** button. 8. Download the displayed QR code.\ This QR code will be used by the holder to claim the credential. **Step 1: Create a Credential configuration** Make the following request to create an [mDoc credentials configuration](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration): ```http title="Request" POST /v2/credentials/mobile/configurations ``` ```json title="Request body" { "type": "com.example.credentialconfiguration.2", "expiresIn": { "months": 1 }, "claimMappings": { "com.example.personaldetails.1": { "name": { "mapFrom": "claims.name", "type": "string", "required": true }, "email": { "mapFrom": "claims.email", "type": "string", "required": true }, "age": { "mapFrom": "claims.age", "type": "number" } } }, "branding": { "name": "My Credential Configuration Tutorial Credential", "description": "Credential Configuration Best Practices", "backgroundColor": "#d82db3ff" }, "claimSourceId": "", "includeStatus": true } ``` * `claimSourceId` : If you completed the [Claim source tutorial](/docs/issuance/claims-source/tutorial), you can replace this with the ID of the claim source you created. Otherwise, omit this line from the request body. *Response* ```json title="Response body" { "id": "294868aa-3814-4a50-9862-5ff48381a8e5" //... rest of your credential configuration } ``` * `id` : Unique identifier for the created mDocs credentials configuration. This ID is used to create a Credential offer in the next step. **Step 2: Create a Credential offer** You can now generate a [Credential offer](/docs/issuance/credential-offer/overview) and share it with the intended holder. Make sure you create a credential offer that matches the flow you are implementing. Make the following request to [generate a new Pre-authorized Code flow Credential offer](/docs/issuance/pre-authorized-code/api-reference#create-credential-offer): ```http title="Request" POST /v1/openid/offers/pre-authorized ``` ```json title="Request body" { "credentials": ["707e920a-f342-443b-ae24-6946b7b5033e"], // [!code highlight] "transactionCodeConfiguration": { "inputMode": "numeric", "description": "Please enter the one-time code that was sent to you via email." }, "claims": { "name": "John Doe", // [!code highlight] "email": "" // [!code highlight] }, "expiresIn": { "minutes": 10 } } ``` * `credentials` : Populate the array with the `id` element returned in the response when you created an mDocs credentials configuration in the previous step. * `name` : You can use any value here. It will be mapped to the `name` claim in the issued credential. * `email` : You can use any value here. It will be mapped to the `email` claim in the issued credential. In this example we are manually inserting the `email` and `age` claims into the credential offer request. In a real-world scenario, you would want to dynamically populate this value based on the authenticated user. *Response* ```json title="Response body" { "id": "e6e5e43c-8053-464a-aca4-ca43da765c97", "uri": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Flearn.vii.au01.mattr.global%22%2C%22credentials%22%3A%5B%22b7b380be-1467-446b-8683-1d131e6532be%22%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%229K3N9UPQD-Hs5f5-gPx0fRJEmb1XTZsWszzXL024pV0%22%2C%22tx_code%22%3A%7B%22length%22%3A6%2C%22input_mode%22%3A%22numeric%22%2C%22description%22%3A%22Please%20enter%20the%20one-time%20code%20that%20was%20sent%20to%20you%20via%20email.%22%7D%7D%7D%7D", // [!code focus] "userId": "6e30dd69-c867-4279-afd3-e6619253b4a4", "expiresAt": "2025-08-25T01:39:00.523Z", "transactionCode": "763677" // [!code focus] } ``` You will need to obtain two important elements from the response: * `transactionCode` : This is the one-time code that the user will need to provide in order to claim the credential. In production deployments you will need to provide it to the user through a different channel (e.g., email, SMS). * `uri` : This URI can be used by a digital wallet to trigger the OID4VCI workflow. Use one of the following tools to convert the `uri` value to a QR code (make sure you use the `Plain text` option where available) and save the generated QR code as an image file. * [https://www.the-qrcode-generator.com/](https://www.the-qrcode-generator.com/) * [http://goqr.me/api/](http://goqr.me/api/) * [https://www.qr-code-generator.com/](https://www.qr-code-generator.com/) MATTR is not affiliated with any of these service providers and cannot vouch for their offerings. Make the following request to [generate a new Credential offer](/docs/issuance/authorization-code/api-reference#create-credential-offer): ```http title="Request" POST /v1/openid/offers ``` ```json title="Request body" { "credentials": [""] } ``` * `credentials`: Replace this with the `id` value returned in the response when you created the mDocs credentials configuration in the previous step. *Response* The response will include a `uri` element which can be used by a digital wallet to trigger the OID4VCI workflow. Use one of the following tools to convert the `uri` value to a QR code (make sure you use the `Plain text` option where available): * [https://www.the-qrcode-generator.com/](https://www.the-qrcode-generator.com/) * [http://goqr.me/api/](http://goqr.me/api/) * [https://www.qr-code-generator.com/](https://www.qr-code-generator.com/) MATTR is not affiliated with any of these service providers and cannot vouch for their offerings. Save the generated QR code on your computer. ## Test the workflow [#test-the-workflow] 1. Open the GO hold example app. 2. Select **Scan**. 3. Scan the QR code generated in the previous step. 4. Review the credential offer and select **Accept**. 5. Follow the issuance workflow instructions to claim the credential. * If you are testing an Authorization Code flow, you will be redirected to the configured authentication provider to authenticate. After a successful authentication you will be redirected back to the GO hold app to continue the issuance workflow. * If you are testing a Pre-authorized Code flow, you will also be prompted to enter the `transactionCode` value returned in the response when you created the credential offer. You should now have a new credential your MATTR GO Hold app. If you are using a claim source as part of your credential configuration, ensure that it is correctly set up and accessible. Issuance may fail if the claims source is not reachable or does not return the expected claims. ## What's next? [#whats-next] Check out more resources on MATTR Learn that will enable you to: * Configure a [Claims source](/docs/issuance/claims-source/tutorial) to retrieve data from compatible data sources and use it in the issued credential. * Configure an [Interaction hook](/docs/issuance/authorization-code/interaction-hook/tutorial) to redirect the user to custom components as part of the issuance workflow. # ISO-compliant credentials URL: /docs/issuance/credential-configuration/iso-compliant-credentials Description: Learn how to create credential configurations that result in ISO-compliant credentials This guide helps you create credential configurations that comply with specific ISO standards for mobile credentials (mDocs). It provides detailed information about the namespaces, data elements, and requirements for issuing standards-compliant digital credentials. The guide covers: * **ISO/IEC 18013-5:2021:** Mobile Driving License (mDL) standard, including mandatory and optional data elements * **ISO/IEC TS 23220-2:2024:** Reusable namespaces and claims for mobile documents Each section includes complete claim definitions, compliance requirements, and practical examples to help you implement ISO-compliant credential issuance. ## ISO/IEC 18013-5:2021 namespace and claims [#isoiec-18013-52021-namespace-and-claims] ISO/IEC 18013-5:2021 is an international standard that defines the mobile Driving License (mDL) — a digital representation of a physical driver's license. It specifies the data elements, security features, and communication protocols for issuing and verifying mDL credentials. The standard defines a namespace `org.iso.18013.5.1` containing mandatory and optional data elements that can be included in an mDL credential. The example below shows a `claimMappings` object you can use in your credential configuration to issue ISO/IEC 18013-5:2021 compliant mDL credentials. This example includes all the mandatory and commonly used optional claims defined in Table 5, Section 7.2 of the standard. In practice, you would select only the claims that are relevant to your use case and comply with local regulations: ```json title="Example claimMappings object for ISO/IEC 18013-5:2021 compliance" { "org.iso.18013.5.1": { "family_name": { // Last name, surname, or primary identifier of the mDL holder (mandatory) "type": "string", "mapFrom": "claims.family_name" }, "given_name": { // First name(s) or other name(s) of the mDL holder (mandatory) "type": "string", "mapFrom": "claims.given_name" }, "birth_date": { // Date of birth of the mDL holder in full-date format as defined in RFC 3339 (mandatory) "type": "date", "mapFrom": "claims.birth_date" }, "issue_date": { // Date when the mDL was issued in full-date format as defined in RFC 3339 (mandatory) "type": "date", "mapFrom": "claims.issue_date" }, "expiry_date": { // Date when the mDL will expire in full-date format as defined in RFC 3339 (mandatory) "type": "date", "mapFrom": "claims.expiry_date" }, "issuing_country": { // Alpha-2 country code as defined in ISO 3166-1 of the issuing authority's country (mandatory) // Must match the country of the IACA at the root of the certificate chain that signs the mDL (see callout below) "type": "string", "defaultValue": "US" }, "issuing_authority": { // Issuing authority name (mandatory) "type": "string", "defaultValue": "State Department of Motor Vehicles" }, "document_number": { // The number assigned or calculated by the issuing authority (mandatory) "type": "string", "mapFrom": "claims.document_number" }, "portrait": { // Portrait image of the mDL holder (mandatory) "type": "binary", "mapFrom": "claims.portrait" }, "driving_privileges": { // Driving privileges of the mDL holder (mandatory) // Array of objects containing vehicle category code, issue date, expiry date, codes, etc. "type": "array", "mapFrom": "claims.driving_privileges" }, "un_distinguishing_sign": { // Distinguishing sign of the issuing country according to ISO/IEC 18013-1:2018, // Annex F (UN distinguishing sign) (mandatory) "type": "string", "defaultValue": "USA" }, "administrative_number": { // An audit control number assigned by the issuing authority (optional) "type": "string", "mapFrom": "claims.administrative_number" }, "sex": { // mDL holder's sex using values as defined in ISO/IEC 5218 (optional) // 0 = not known, 1 = male, 2 = female, 9 = not applicable "type": "number", "mapFrom": "claims.sex" }, "height": { // mDL holder's height in centimetres (optional) "type": "number", "mapFrom": "claims.height" }, "weight": { // mDL holder's weight in kilograms (optional) "type": "number", "mapFrom": "claims.weight" }, "eye_colour": { // mDL holder's eye colour (optional) // Valid values: black, blue, brown, dichromatic, grey, green, hazel, maroon, pink, unknown "type": "string", "mapFrom": "claims.eye_colour" }, "hair_colour": { // mDL holder's hair colour (optional) // Valid values: bald, black, blond, brown, grey, red, auburn, sandy, white, unknown "type": "string", "mapFrom": "claims.hair_colour" }, "birth_place": { // Country and municipality or state/province where the mDL holder was born (optional) "type": "string", "mapFrom": "claims.birth_place" }, "resident_address": { // The place where the mDL holder resides and/or may be contacted (optional) "type": "string", "mapFrom": "claims.resident_address" }, "portrait_capture_date": { // Date when portrait was taken in full-date format as defined in RFC 3339 (optional) "type": "date", "mapFrom": "claims.portrait_capture_date" }, "age_in_years": { // The age of the mDL holder (optional) "type": "number", "mapFrom": "claims.age_in_years" }, "age_birth_year": { // The year when the mDL holder was born (optional) "type": "number", "mapFrom": "claims.age_birth_year" }, "age_over_18": { // Indication whether the mDL holder is over 18 years old (optional) "type": "boolean", "mapFrom": "claims.age_over_18" }, "age_over_21": { // Indication whether the mDL holder is over 21 years old (optional) "type": "boolean", "mapFrom": "claims.age_over_21" }, "issuing_jurisdiction": { // Subdivision code as defined in ISO 3166-2 of the issuing authority (optional) "type": "string", "mapFrom": "claims.issuing_jurisdiction" }, "nationality": { // Nationality of the mDL holder as an alpha-2 country code as defined in ISO 3166-1 (optional) "type": "string", "mapFrom": "claims.nationality" }, "resident_city": { // The city where the mDL holder lives (optional) "type": "string", "mapFrom": "claims.resident_city" }, "resident_state": { // The state/province/district where the mDL holder lives (optional) "type": "string", "mapFrom": "claims.resident_state" }, "resident_postal_code": { // The postal code of the mDL holder (optional) "type": "string", "mapFrom": "claims.resident_postal_code" }, "resident_country": { // The country where the mDL holder lives as an alpha-2 code as defined in ISO 3166-1 (optional) "type": "string", "mapFrom": "claims.resident_country" }, "family_name_national_character": { // Family name of the mDL holder in national characters (optional, deprecated) // Note: This data element is deprecated and should not be used for new implementations "type": "string", "mapFrom": "claims.family_name_national_character" }, "given_name_national_character": { // Given name of the mDL holder in national characters (optional, deprecated) // Note: This data element is deprecated and should not be used for new implementations "type": "string", "mapFrom": "claims.given_name_national_character" }, "signature_usual_mark": { // Image of the signature or usual mark of the mDL holder (optional) "type": "binary", "mapFrom": "claims.signature_usual_mark" } } // ... rest of your credential configuration (credentialType, validityPeriod, etc.) } ``` mDLs are signed by a Document Signer Certificate (DSC) that chains to an IACA. The `issuing_country` value must match the country of that IACA, and `issuing_jurisdiction` (if included) must match the IACA's State or Province. `issuing_country` is an [ISO 3166-1 alpha-2 code](https://www.iso.org/glossary-for-iso-3166.html) such as `NZ`, and `issuing_jurisdiction` is an [ISO 3166-2 code](https://www.iso.org/glossary-for-iso-3166.html). If these values do not match the IACA, issuance fails. Set these values to reflect your own IACA rather than copying the example values. ISO/IEC 18013-5:2021 specifies additional age attestation claims (such as `age_over_16`, `age_over_25`, etc.) that can be included based on jurisdictional requirements. The standard also requires that mDL credentials have a validity period not exceeding 427 days (approximately 14 months) from the issue date. ### Mandatory and optional data elements for compliant mDL issuance [#mandatory-and-optional-data-elements-for-compliant-mdl-issuance] According to ISO/IEC 18013-5:2021 (Table 5, Section 7.2), a compliant mobile Driving License must include the following data elements: **Mandatory data elements** These data elements must be present in every compliant mDL credential: * `family_name` — Last name, surname, or primary identifier of the mDL holder * `given_name` — First name(s) or other name(s) of the mDL holder * `birth_date` — Date of birth in full-date format (RFC 3339) * `issue_date` — Date when the mDL was issued * `expiry_date` — Date when the mDL will expire * `issuing_country` — Alpha-2 country code (ISO 3166-1) of the issuing authority's country * `issuing_authority` — Name of the issuing authority * `document_number` — The number assigned or calculated by the issuing authority * `portrait` — Portrait image of the mDL holder * `driving_privileges` — Array containing the driving categories and privileges * `un_distinguishing_sign` — Distinguishing sign of the issuing country (ISO/IEC 18013-1:2018, Annex F) **Optional data elements** These data elements may be included based on jurisdictional requirements and issuer policies: * `administrative_number` — Audit control number * `sex` — mDL holder's sex (ISO/IEC 5218 values) * `height` — Height in centimetres * `weight` — Weight in kilograms * `eye_colour` — Eye color (predefined values) * `hair_colour` — Hair color (predefined values) * `birth_place` — Country and municipality where the holder was born * `resident_address` — Residential address * `portrait_capture_date` — Date when the portrait was taken * `age_in_years` — Age of the holder * `age_birth_year` — Birth year of the holder * `age_over_18`, `age_over_21` — Age attestation booleans (additional age thresholds may be included based on jurisdictional needs) * `issuing_jurisdiction` — Subdivision code (ISO 3166-2) * `nationality` — Nationality as alpha-2 country code (ISO 3166-1) * `resident_city` — City where the holder resides * `resident_state` — State/province/district where the holder resides * `resident_postal_code` — Postal code * `resident_country` — Country of residence as alpha-2 code (ISO 3166-1) * `signature_usual_mark` — Image of the holder's signature or usual mark The data elements `family_name_national_character` and `given_name_national_character` are deprecated in ISO/IEC 18013-5:2021 and should not be used for new implementations. ### Minimal claims mapping object for ISO/IEC 18013-5:2021 compliance [#minimal-claims-mapping-object-for-isoiec-18013-52021-compliance] The following example shows a minimal `claimMappings` object for your credential configuration that includes only the mandatory data elements required for ISO/IEC 18013-5:2021 compliance. Use this as the foundation of your credential configuration and extend it with optional data elements based on specific jurisdictional requirements: ```json title="Minimal claimMappings object for ISO/IEC 18013-5:2021 compliance" { "org.iso.18013.5.1": { "family_name": { "type": "string", "mapFrom": "claims.family_name" }, "given_name": { "type": "string", "mapFrom": "claims.given_name" }, "birth_date": { "type": "date", "mapFrom": "claims.birth_date" }, "issue_date": { "type": "date", "mapFrom": "claims.issue_date" }, "expiry_date": { "type": "date", "mapFrom": "claims.expiry_date" }, "issuing_country": { "type": "string", "defaultValue": "US" }, "issuing_authority": { "type": "string", "defaultValue": "State Department of Motor Vehicles" }, "document_number": { "type": "string", "mapFrom": "claims.document_number" }, "portrait": { "type": "binary", "mapFrom": "claims.portrait" }, "driving_privileges": { "type": "array", "mapFrom": "claims.driving_privileges" }, "un_distinguishing_sign": { "type": "string", "defaultValue": "USA" } } // ... rest of your credential configuration } ``` **Key points for your credential configuration:** * **claimMappings**: The object shown above should be included as the `claimMappings` property in your credential configuration. Uses dot notation to map data from the user object (e.g., `claims.family_name`) or default values where appropriate. * **Credential type**: Set `type` to `org.iso.18013.5.1.mDL` to indicate this is a mobile Driving License compliant with ISO/IEC 18013-5:2021 * **Namespace**: All claims are grouped under the `org.iso.18013.5.1` namespace as required by the standard * **Issuing country**: Set `issuing_country` to match the country of the IACA at the root of the certificate chain that signs the mDL. A mismatch causes issuance to fail. Refer to [Certificates](/docs/issuance/certificates/overview) for details * **Validity period**: Must not exceed 427 days as per the standard. Refer to [Credential validity](/docs/issuance/credential-configuration/overview#credential-validity) to learn more about how to set the validity period in your credential configuration * **Portrait**: The portrait image should be provided as binary data in JPEG or JPEG2000 format * **Driving privileges**: This is an array that contains objects specifying vehicle categories and associated privileges. Each privilege object should include: * `vehicle_category_code` — The vehicle category * `issue_date` — When the privilege was granted * `expiry_date` — When the privilege expires * Additional codes or restrictions as applicable When implementing the `driving_privileges` array, ensure each privilege object conforms to the structure defined in ISO/IEC 18013-5:2021, Section 7.2.4. The array must contain at least one valid driving privilege entry. ## ISO/IEC TS 23220-2 namespace and claims [#isoiec-ts-23220-2-namespace-and-claims] ISO/IEC TS 23220-2:2024 is a Technical Specification that defines reusable namespaces and claims. A second edition is currently under development and is expected to be published soon. If compliance with ISO/IEC TS 23220-2:2024 is desired, you can use the reusable namespace and claims in your credential configuration. Reusing these common namespace and claims helps ensure semantic consistency across different credential types. The example below shows a `claimMappings` object you can use in your credential configuration to issue ISO/IEC TS 23220-2:2024 compliant credentials. This example includes all the claims defined in the standard, but in practice you would typically select only the claims that are relevant to your use case: ```json title="Example claimMappings object for ISO/IEC TS 23220-2:2024 compliance" { "org.iso.23220.1": { "family_name_unicode": { // Last name, surname, or primary identifier of the holder (unicode characters) "type": "string", "mapFrom": "claims.family_name_unicode" }, "given_name_unicode": { // First name(s), other name(s), or secondary identifier of the holder "type": "string", "mapFrom": "claims.given_name_unicode" }, "sex": { // Holder's sex using values as defined in ISO/IEC 5218 (0=Unknown, 1=Male, 2=Female, 9=Not applicable) "type": "number", "mapFrom": "claims.sex" }, "height": { // Holder's height in centimetres (uint) "type": "number", "mapFrom": "claims.height" }, "weight": { // Holder's weight in kilograms (uint) "type": "number", "mapFrom": "claims.weight" }, "birthplace": { // Country and municipality or state/province where the holder was born "type": "string", "mapFrom": "claims.birthplace" }, "resident_address_unicode": { // The place where the holder resides and/or may be contacted (street/house number, municipality etc.) "type": "string", "mapFrom": "claims.resident_address_unicode" }, "resident_city_unicode": { // The city/municipality (or equivalent) where the holder lives "type": "string", "mapFrom": "claims.resident_city_unicode" }, "resident_postal_code": { // The postal code of the holder "type": "string", "mapFrom": "claims.resident_postal_code" }, "resident_country": { // The country where the holder lives as a two letter country code // (alpha-2 code) defined in ISO 3166-1 "type": "string", "mapFrom": "claims.resident_country" }, "biometric_template_face": { // A reproduction of the holder's portrait "type": "binary", "mapFrom": "claims.biometric_template_face" }, "portrait": { // Portrait data as specified in ISO/IEC 18013-2:2020, Annex D, // but only JPEG or JPEG2000 shall be supported. "type": "binary", "mapFrom": "claims.portrait" }, "portrait_capture_date": { // Date when portrait was taken "type": "date", "mapFrom": "claims.portrait_capture_date" }, "fingerprint": { // A reproduction of the holder's fingerprint data (TBC) "type": "binary", "mapFrom": "claims.fingerprint" }, "nationality": { // Nationality of the holder as two letter country code (alpha-2 code) // or three letter code (alpha-3 code) defined in ISO 3166-1b "type": "string", "mapFrom": "claims.nationality" }, "business_name_unicode": { // Business name of the holder "type": "string", "mapFrom": "claims.business_name_unicode" }, "organization_name_unicode": { // Name of the legal person "type": "string", "mapFrom": "claims.organization_name_unicode" }, "name_at_birth": { // The name(s) which holder was born "type": "string", "mapFrom": "claims.name_at_birth" }, "telephone_number": { // Telephone number of the holder, including country code // as specified ITU-T E.123 and ITU-T E.164 "type": "string", "mapFrom": "claims.telephone_number" }, "email_address": { // E-mail address of the holder "type": "string", "mapFrom": "claims.email_address" }, "profession": { // Profession of the holder "type": "string", "mapFrom": "claims.profession" }, "title": { // Academic title of the holder "type": "string", "mapFrom": "claims.title" }, "age_in_years": { // The age of the holder "type": "number", "mapFrom": "claims.age_in_years" }, "age_birth_year": { // The year the holder was born "type": "number", "mapFrom": "claims.age_birth_year" }, "age_over_NN": { // e.g., age_over_18, age_over_21 "type": "boolean", "mapFrom": "claims.age_over_NN" }, "issuing_country": { // Country code as alpha 2 and alpha 3 code, defined in ISO 3166-1, // which issued the credential or within which the issuing // authority is located "type": "string", "mapFrom": "claims.issuing_country" }, "issuing_subdivision": { // Subdivision code as defined in ISO 3166-2, which issued // the credential or within which the issuing authority located "type": "string", "mapFrom": "claims.issuing_subdivision" }, "issuing_authority_unicode": { // Name of issuing authority "type": "string", "mapFrom": "claims.issuing_authority_unicode" }, "issue_date": { // Date the credential was issued "type": "date", "mapFrom": "claims.issue_date" }, "expiry_date": { // Date the credential expires "type": "date", "mapFrom": "claims.expiry_date" }, "document_type": { // The document type "type": "string", "mapFrom": "claims.document_type" }, "document_number": { // The number assigned or calculated by the issuing authority "type": "string", "mapFrom": "claims.document_number" } } // ... rest of your credential configuration (credentialType, validityPeriod, etc.) } ``` MATTR VII does not currently support the ISO/IEC TS 23220-2:2024 `birthdate` claim. # Credential configurations URL: /docs/issuance/credential-configuration/overview Credential configurations are templates that define rules and parameters that are used to issue a specific type of digital credential. They are referenced in [Credential offers](/docs/issuance/credential-offer/overview) and define rules and parameters that control the structure and content of the credentials being issued. Every MATTR VII tenant can include multiple Credential configurations to meet different use cases. ## Key considerations [#key-considerations] When creating a new credential configuration, an issuer should take the following factors into account: * **Credential type**: Used to uniquely identify credentials issued via this configuration. * **Credential content**: Decide what information will be included in the credential, and how it will be structured. * **Credential validity**: Set the appropriate validity period to reflect how long the credential should be considered accurate and trustworthy. * **Revocation support**: Determine whether the credential needs to support revocation status updates. * **Branding configuration**: Specify how the credential should appear in a digital wallet. While display conventions are not yet standardized, MATTR issuance and holding capabilities implement a proprietary format to support branding, clarity, and holder trust. * **Availability**: Credential configurations must be publicly available to be used in a credential offer. This is to ensure that wallet applications can always access the configuration when they receive a credential offer. ## Credential type [#credential-type] The credential type is used to differentiate between different types of credentials. It is a unique identifier that specifies the purpose and structure of the credential being issued. For example: * `UniversityDegreeCredential` : Indicates that the credential being issued is a university degree certificate. * `EmployeeIDCredential` : Indicates that the credential being issued is an employee ID card. * `VaccinationCertificate` : Indicates that the credential being issued is a vaccination record. Some credential types are standardized and widely recognized, such as: * `org.iso.18013.5.*.mDL` : Mobile Driving License (mDL) compliant with the ISO/IEC 18013-5:2021 standard. * `org.iso.23220.photoid.1` : Mobile Photo ID compliant with the ISO/IEC 23220-3:2024 standard. * `eu.europa.ec.eudi.pid.1` : Mobile Person Identification Data compliant with the EU Digital Identity framework. * `org.iso.7367.1.mVC` : Mobile Vehicle Certificate compliant with the ISO/IEC 7367 standard. * `org.micov.1` : eHealth / Mobile International Certificate of Vaccination compliant with the WHO guidelines. * `org.iso.23220.1.jp.mnc` : Japanese My Number Card compliant with the ISO/IEC 23220-3:2024 standard. Some MATTR VII issuance logic is based on the set credential type. For example, when set to `org.iso.18013.5.*.mDL` (where \* is a positive integer), MATTR VII recognizes that this is an attempt to create an mDL credential configuration and will fail if the validity period is set to 427 days or more to comply with ISO/IEC 18013-5:2021. *Best Practices* To comply with the emerging ISO/IEC TS 23220 requirements, credential types must be named using a reverse domain name that is under the control of the type designer. For example: * `com.example.credentialtype.1` This approach is necessary because registration in the ISO/IEC 23220-7 type registry requires proof of domain ownership. It is also recommended to include a version identifier (e.g., .1, .2) at the end of the credential type name. This makes it possible to introduce new versions without breaking compatibility and ensures it is always clear which version of the type is being referenced. ## Credential content [#credential-content] The credential content defines the specific information that will be included in the credential, commonly referred to as *claims*. These claims can include various details about the credential holder such as their name, email address, and any other relevant data. ### Namespaces [#namespaces] Namespaces provide a structured way to organize claims within a credential. They act like containers or “labels” that group related claims together, ensuring that their meaning is unambiguous and consistent across different contexts. Using namespaces helps avoid confusion when similar claims appear in different credential types, and it enables reuse of standard definitions across multiple contexts. For instance, both a university credential and a driver’s license might include a claim called `issue_date`, but the meaning differs — in the university context it refers to when the student ID was issued, while in the driver’s license it refers to when the license was granted. By grouping these under separate namespaces (`org.example.university.1.issue_date` vs. `org.example.driverslicense.1.issue_date`), each claim is clearly contextualized, avoiding confusion while still allowing both credentials to coexist in the same ecosystem. claimMappings can be defined in two ways: * **Flat namespace**: All claims are expressed at the same level. * **Multiple/grouped namespaces**: Claims are grouped into structured objects under different namespaces. For example, the ISO/IEC 18013-5 specification for mobile Driving Licenses (mDLs) defines a standard `org.iso.18013.5.1` namespace that covers a wide set of claims such as `family_name`, `given_name`, `birth_date`, and `portrait`. This namespace is not exclusive to driver licenses. It can be reused across other mDoc types, such as a mobile ID (mID) or a residence permit, which also need to represent personal identity attributes. Regardless of the approach, the semantics of common claims must remain unchanged. For example, `first_name` should always refer to the credential holder’s first name. Any additional claims should be clearly named or placed under an appropriate namespace to prevent ambiguity. The following examples illustrate both approaches: ```json title="Flat namespace" { "claimMappings": { "org.example.studentid.1": { "first_name": { "defaultValue": "Alice", "type": "string" }, "last_name": { "defaultValue": "Miller", "type": "string" }, "birth_date": { "defaultValue": "2001-05-14", "type": "date" }, "university_name": { "defaultValue": "Kakapo University of Technology", "type": "string" }, "student_id": { "defaultValue": "12345678", "type": "string" }, "program": { "defaultValue": "Computer Science", "type": "string" }, "enrollment_year": { "defaultValue": 2020, "type": "number" } } } } ``` ```json title="Grouped namespaces" { "claimMappings": { "org.example.common.1": { "first_name": { "defaultValue": "Alice", "type": "string" }, "last_name": { "defaultValue": "Miller", "type": "string" }, "birth_date": { "defaultValue": "2001-05-14", "type": "date" } }, "org.example.university.1": { "university_name": { "defaultValue": "Kakapo University of Technology", "type": "string" }, "student_id": { "defaultValue": "12345678", "type": "string" } }, "org.example.academic.1": { "program": { "defaultValue": "Computer Science", "type": "string" }, "enrollment_year": { "defaultValue": 2020, "type": "number" } } } } ``` *Best Practices* * It is recommended to append a version identifier (e.g., .1, .2) to namespaces (similar to credential types). This allows new versions of a namespace to be introduced without breaking interoperability and makes it clear which revision a given namespace belongs to. * Using multiple namespaces may introduce a small binary size overhead, but it provides better scalability. This is particularly useful when the same namespace is reused across different credential types. Reusing namespaces ensures consistency in meaning while avoiding duplication. ### Claims mapping [#claims-mapping] Credential configurations control the structure and content of issued credentials by defining the mapping of claims from an internal `user` object into the issued credential. The `user` object is populated during the OID4VCI workflow and contains information about the user that is being issued a credential. This information can come from various sources: * In an [Authorization Code flow](/docs/issuance/authorization-code/overview) these can be retrieved from the configured [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) and/or [Interaction hook](/docs/issuance/authorization-code/interaction-hook/overview). * In a [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview) these can be provided directly in the credential offer by the issuer. * In either flow a compatible [Claims source](/docs/issuance/claims-source/overview) can be configured to provide additional user attributes. When using the Authorization Code flow, the claims contributed by your Authentication provider to the `user` object depend on what the provider returns (for example, via the ID token or userinfo endpoint). Not all providers support all claims by default. Before defining your claim mappings, verify that your provider can supply the required claims from this source. Additional attributes can still be added to the `user` object via an Interaction hook or Claims source. See [Ensuring claim availability](/docs/issuance/authorization-code/authentication-provider/overview#ensuring-claim-availability) for guidance on checking your provider's supported claims and scopes. This is an example of what a `user` object looks like: ```json title="User object example" { "authenticationProvider": { "url": "https://account.example.com", "subjectId": "145214ad-3635-4aff-b51d-61d69a3c8eee" }, "claims": { "given_name": "John", "family_name": "Doe", "email": "john.doe@example.com", "address": { "formatted": "123FooRd,BarWorld" } } } ``` * `authenticationProvider` (only provided for users in an [Authorization Code flow](/docs/issuance/authorization-code/overview)) : This object contains * `url` : References the [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) that is used to authenticate this user. * `subjectId` : Indicates the unique identifier of the user with this Authentication provider. * `claims` : This object is used to hold claims fetched as part of the OID4VCI workflow. The claims object can contain simple key-value pairs (e.g., `given_name`, `family_name`, `email`) or nested objects (e.g., `address`). The actual mapping from the user object into the issued credential is defined in the `claimMapping` object in the credential configuration. Each item in the object represents a credential claim and can be mapped in different ways, depending on the use case. Here is an example of a `claimMapping` object: ```json title="claimMappings object example" { "claimMappings": { "dateOfBirth": { "mapFrom": "claims.dateOfBirth", "required": true, "type": "string" }, "email": { "mapFrom": "claims.email", "required": false, "defaultValue": "Not available", "type": "string" } } } ``` * `mapFrom` : This is used to map a claim in the issued credential to a specific attribute in the claims object. In this example, the `dateOfBirth` claim in the issued credential is mapped directly from `claims.dateOfBirth` in the user object, and the `email` claim is mapped from `claims.email`. * `type` : This defines the data type of the claim. Refer to the API Reference for [available types](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=claimMappings/additionalProperties/additionalProperties\&t=request) and their usage. In this example, both `dateOfBirth` and `email` are defined as strings. * `required` : When set to `true`, the claim must be provided during issuance. If it is not provided and no `defaultValue` is set, issuance will fail. If set to `false` or not present, the claim is optional and issuance will succeed even if the claim is not provided. In this example, `dateOfBirth` is a required claim, while `email` is optional. * `defaultValue` : This is used to set a default value for the claim if it is not provided during issuance. This is useful for optional claims where a sensible default can be applied. In this example, `Not available` is set as the default value for the `email` claim. The following logic is applied when combining these properties: * Either `mapFrom` or `defaultValue` must be provided for each claim. * If `mapFrom` is provided and the claim is found in the `user` object, its value will be used. * If `mapFrom` is provided but the claim is **not** found in the `user` object: * If `required` is `true` and no `defaultValue` is provided, issuance will fail. * If `required` is `false` or not present and a `defaultValue` is provided, the `defaultValue` will be used. * If `required` is `false` or not present and no `defaultValue` is provided, the claim will be omitted from the issued credential. * If only `defaultValue` is provided (without `mapFrom`), the claim will always be set to the `defaultValue`. *Best Practices* * Some credential types require a portrait claim. You must provide the portrait as a binary claim type. * Maximum supported portrait file size is 500KB. Keep files below 50KB for better performance and user experience. * When creating credential configurations, consider limitations on the overall size of requests to create credential offers. The total size of the request cannot exceed 500KB in both flows, but the practical impact differs: * Authorization Code flow: This is usually not an issue, as claims are typically retrieved from the authentication provider or claims source rather than included in the request. * Pre-authorized Code flow: Claims are supplied in the request itself, so the limit is most relevant when including large claims such as images. If holders present credentials over Bluetooth Low Energy (BLE), keep payloads as small as practical, as large claims can degrade the transfer experience. Reserve larger payloads for remote presentation flows. #### Avoiding issuance failures due to claim mapping [#avoiding-issuance-failures-due-to-claim-mapping] In accordance with OID4VCI, an issued credential must contain at least one claim. This ensures that every credential carries meaningful information about the holder. The `claimMappings` object in a credential configuration defines *how* claims should be mapped into the issued credential. It does not itself guarantee that claims will be present at issuance time. Instead, it specifies where claim values should be sourced from (for example, from the user object or a configured Claims source), and how they should be handled if missing. Issuance failures related to claims typically occur at issuance time, not when the credential configuration is created. This is because the actual presence of claim values is only determined during the OID4VCI workflow when a credential offer is processed and a credential is issued. For example: * If a credential configuration defines claim mappings but no matching data is available in the user object or configured Claims source for a particular user, the resulting credential may contain no claims. * If all mapped claims are optional and no values (or `defaultValues`) resolve during issuance, the credential would effectively be empty. Because OID4VCI requires at least one claim in an issued credential, such issuance attempts will fail. For this reason, issuers should carefully design their claim mappings and data sources to ensure that at least one claim will always resolve during issuance: * Ensure that at least one claim is guaranteed to resolve for all intended users. * Use `required: true` where appropriate to prevent silent omission of critical claims. * Provide sensible `defaultValues` where possible to avoid issuance failures due to missing upstream data. * Validate that your configured Claims source can reliably return data for the user population you intend to issue to. #### Complex credential structures [#complex-credential-structures] Credential configurations support complex data structures, including nested objects and arrays. This enables flexible modeling of real-world data within credential schemas. The following considerations apply when defining complex structures in the `claimMappings` object: * Only standard JSON data types are supported. * When array or object claims are used, selective disclosure works at the array/object level only. Individual elements cannot be disclosed or hidden independently. This means the holder can either share the entire array/object or omit it. For example, if multiple bank accounts are listed in an array, presenting that claim reveals all accounts, which may have privacy implications. #### Localized display labels [#localized-display-labels] Each claim in `claimMappings` can optionally include a `display` array that provides localized labels for the claim. This allows wallet applications to render claim names in the holder's preferred language, in line with [OID4VCI §B.2](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-claims-description-for-issu). Each entry in the array pairs an optional `name` (human-readable label) with an optional `locale` ([BCP47](https://www.rfc-editor.org/rfc/rfc5646.html) language tag). For example: ```json title="Claim with localized display labels" { "claimMappings": { "org.iso.18013.5.1": { "given_name": { "mapFrom": "claims.given_name", "type": "string", "required": true, "display": [ { "name": "Given Name", "locale": "en-US" }, { "name": "Vorname", "locale": "de-DE" } ] } } } } ``` When configured, these entries are surfaced in the issuer metadata at `/.well-known/openid-credential-issuer`, where wallets can read them during the credential offer flow. The following constraints apply: * The `display` array is optional. When present, it must contain at least one entry. * `locale` values must be unique within a given claim's `display` array. Providing multiple entries with the same `locale` is not allowed and would result in an error. * `display` is currently only supported for mDoc credential configurations. ## Credential validity [#credential-validity] Credential validity defines the period during which the credential is considered valid and can be used for its intended purpose. Invalid credentials would fail verification by any standards compliant relying party/verifier. The credential validity can be set using a combination of three properties in the credential configuration: * Relative validity: * [`expiresIn`](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=expiresIn\&t=request) : Determines when will the credential expire in relation to its issuance time and date. * Absolute validity: * [`validFrom`](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=validFrom\&t=request) : Specifies an absolute date and time from which the credential is considered valid. For example, a credential could become valid on a fixed date, regardless of when it is issued. * [`validUntil`](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=validUntil\&t=request) : Specifies an absolute date and time at which the credential expires. For example, a credential could be valid until a fixed date, regardless of when it is issued. Here are two examples of how these properties can be used: ```json title="Setting relative credential validity" { "expiresIn": { "days": 30 } // Rest of the credential configuration } ``` This credential will be valid for 30 days from the date and time it is issued. ```json title="Setting absolute credential validity" { "validFrom": { "mapFrom": "claims.validFrom" }, "validUntil": { "mapFrom": "claims.validUntil" } // Rest of the credential configuration } ``` In this example, the `validFrom` and `validUntil` claims are mapped from the `claims` object. This allows the issuer to specify exact dates for when the credential becomes valid and when it expires based on values provided during issuance. Note that in this example `expiresIn` must also be set, as it is a required property. Its value will be ignored as both `validFrom` and `validUntil` are provided. The following logic is applied when combining these properties: **Summary of validity decision logic:** 1. If `validFrom` is included in the credential configuration: * If `validFrom` is also provided in user claims, use the value from user claims. * Otherwise, use the value from the credential configuration. 2. If `validFrom` is not included, set the credential's valid from date to the date of issuance. 3. If `validUntil` is included in the credential configuration: * If `validUntil` is also provided in user claims, use the value from user claims. * Otherwise, use the value from the credential configuration. 4. If `validUntil` is not included, set the credential's valid until date to the date of issuance plus `expiresIn`. 5. Additional validation: * If `validUntil` is malformed, issuance fails. * If `validUntil` is earlier than `validFrom`, issuance fails. * Otherwise, issuance succeeds. ### Validity failures [#validity-failures] Credential issuance can fail due to validity misconfiguration in the following scenarios: * Invalid parameters combinations: * `validUntil` or `validFrom` are in the past. * `validFrom` is after `validUntil`. * `expiresIn` is before the `validFrom` date and time (when both are provided). * Invalid certificates: The validity period of a credential must not exceed the validity period of the IACA or DSC certificates that form part of its [chain of trust](/docs/concepts/chain-of-trust). If the `expiresIn`, `validFrom`, or `validUntil` values extend beyond the validity of the active IACA or any active DSC at the time of issuance, the credential issuance process will fail. For details on how certificates are selected during mDoc signing, see [certificate selection](/docs/issuance/certificates/overview#certificate-selection). * mDL-specific failure: * When the credential type is set to `org.iso.18013.5.*.mDL` (where \* is a positive integer), and the `expiresIn` value is set to 427 days or more. * When the credential type is set to `org.iso.18013.5.*.mDL` (where \* is a positive integer), and the `validUntil` date is more than 427 days after the issuance date or the `validFrom` date (if provided). Certificate validity and credential type checks are enforced only **at the time of credential issuance**, not when a credential configuration template is created. ## Revocation support [#revocation-support] MATTR VII allows you to update the status of issued credentials, enabling you to revoke or suspend credentials when necessary. Verifiers can check the status of a presented credential without compromising the holder’s privacy. Refer to [revocation](/docs/issuance/revocation/overview) for more information. Revocation support is configured using the [`includeStatus`](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=includeStatus\&t=request) boolean in the credential configuration. When set to `true`, issued credentials include a `status` object, which refers a [Status list](/docs/issuance/revocation/overview#status-list) where the credential revocation status is indicated. ## Credential branding [#credential-branding] Credential configurations can include information regarding how to brand them when they are displayed in a digital wallet. This is useful for enhancing the user experience and ensuring that the credential is easily recognizable. While display conventions are not yet standardized, MATTR holding and issuance capabilities implement a proprietary format to support branding, clarity, and holder trust. This format includes the following elements: Credential branding explainer * `name` : Displayed on the top part of the credential. * `description` : Displayed below the name field. * `backgroundColor` : Applied as the credential background color. * `watermarkImage` : Displayed as a pattern on top of the credential. * `issuerLogo` : Displayed on the bottom part of the credential. * `issuerIcon` : Displayed next to the issuer’s name. Refer to the [API Reference](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration!path=branding\&t=request) for specific requirements for each of these elements. ## Availability [#availability] Once you create a credential configuration, its details are available on your tenant's `./well-known/openid-credential-issuer` endpoint (`https://{your_tenant_url}/.well-known/openid-credential-issuer`) so that credentials available from an issuer can be looked up by wallet applications. If this endpoint is not resolvable, OID4VCI workflows would fail. This is especially important when configuring a [Custom domain](/docs/platform-management/custom-domain-overview), which requires creating a redirect from your custom domain URL to your MATTR VII tenant URL where this resource is available. # Credential reports URL: /docs/issuance/credential-reports/guide Description: Learn how to generate credential reports to track issuance and status change operations across your tenant, broken down by day and credential type. Credential reports provide a summary of credential lifecycle operations across your tenant. You can use them to track issuance volumes and status changes (such as revocations) for mDoc and CWT credentials, broken down by day and credential type. This gives you visibility into how credentials are being managed over time, without needing to query individual events. ## Use cases [#use-cases] Credential reports help you answer questions like: * **How many credentials were issued this month?** Track daily issuance volumes to understand adoption trends and usage patterns. * **How many credentials have been revoked?** Monitor status change operations to stay on top of credential lifecycle management. * **What types of credentials are being issued?** Break down issuance by credential type (e.g. `org.iso.18013.5.1.mDL`) to understand which credential profiles are most active. * **Are issuance volumes consistent?** Identify unexpected spikes or drops in activity that may indicate integration issues or changes in demand. ## Report structure [#report-structure] Each report groups operations by several dimensions so you can analyze trends over time: * Operation: The type of credential lifecycle operation, such as `issued` or `status_changed`. * Credential profile: The credential profile, such as `mdoc` or `cwt`. * Credential type: The specific credential type associated with the operation, if applicable (e.g. `org.iso.18013.5.1.mDL` or `VerifiedEmployee`). * Date: The date the operations occurred. ## Generate a credential report [#generate-a-credential-report] 1. Select **Analytics** under **Platform Management** in the sidebar. 2. Use the *Date Range* selector to choose the reporting period. Note that the maximum range for a single report is 31 days, and that the report will include all operations that occurred from the start date at 00:00:00 to the end date at 23:59:59 in the user's timezone. 3. Use the *Credential format* checkboxes to select which credential profiles to include (mDoc, CWT, or both). 4. Click **Generate** to view the results.\ The resulting table will show you the count of operations matching each combination of dimensions (operation, credential profile, credential type) for each date in the selected range. 5. Select **Download CSV** to export the full report data for further analysis, or **Done** to return to the Analytics overview. Make the following API request to [create a credential report](https://learn.mattr.global/docs/api-reference/platform/analytics/createCredentialReport) for your tenant: ```http title="Request" POST /v1/events/credential-reports ``` ```json title="Request body" { "timezoneOffset": "+02:00", "startDate": "2026-02-01", "endDate": "2026-02-28", "profiles": ["cwt", "mdoc"], "limit": 500, "cursor": "b2Zmc2V0PTM=" } ``` * `timezoneOffset`: Timezone offset in ISO-8601 format (e.g. `+02:00`, `-05:00`). Determines how operation timestamps are grouped by date. For example, an operation at `2026-02-25T22:30:00Z` would be counted towards `2026-02-26` when using `+02:00`, but towards `2026-02-25` when using `-05:00`. * `startDate`: Start date for the report (inclusive), interpreted in the specified timezone. * `endDate`: End date for the report (inclusive), interpreted in the specified timezone. * `profiles`: Credential profiles to include in the report. Accepted values: `cwt`, `mdoc`. * `limit` *(optional)*: Maximum number of entries to return. Defaults to `1000` if omitted. * `cursor` *(optional)*: Pagination cursor. If omitted, results start from the most recent entry. *Response* ```json title="Response body" { "data": [ { "date": "2026-02-25", "operation": "issued", "profile": "mdoc", "credentialType": "org.iso.18013.5.1.mDL", "count": 5 }, { "date": "2026-02-25", "operation": "status_changed", "profile": "cwt", "count": 2 }, { "date": "2026-02-26", "operation": "issued", "profile": "cwt", "credentialType": "VerifiedEmployee", "count": 12 } ] } ``` * `data`: An array of report entries, each representing a count of operations matching the specified criteria on a given date. Each entry includes the following fields: * `date`: The date the operations occurred. * `operation`: The type of credential lifecycle operation: `issued` (a credential was issued) or `status_changed` (a credential's status was changed, e.g. revoked). * `profile`: The credential profile: `mdoc` or `cwt`. * `credentialType`: The credential type associated with the operation, if applicable. * `count`: The total number of operations matching these criteria on this date. The `data` array may contain multiple entries for the same date when different operations, profiles, or credential types were involved. In the example above: * 5 mDoc mobile driving licences were issued on 25 February 2026. * 2 CWT credential status changes (e.g. revocations) occurred on the same date. * 12 CWT `VerifiedEmployee` credentials were issued on 26 February 2026. # API Reference URL: /docs/issuance/credential-issuance/api-reference ## Request authorization for access to resources [#request-authorization-for-access-to-resources] ## Exchange authorization code for access token [#exchange-authorization-code-for-access-token] ## Issue a verifiable credential [#issue-a-verifiable-credential] # End-to-End Encryption URL: /docs/issuance/credential-issuance/e2e-encryption End-to-End Encryption is offered as a closed beta preview feature and is not generally available yet. If you are interested in trying this feature, please [contact us](mailto:dev-support@mattr.global). ## Overview [#overview] End-to-End Encryption (E2E Encryption) provides an additional layer of security for credential issuance workflows where intermediary services are involved between the credential issuer and the wallet application. In some credential issuance scenarios, a wallet application's backend server acts as a proxy or middleman between the credential issuer (MATTR VII) and individual wallet app instances. In these architectures, the credential response passes through the wallet provider's backend infrastructure before reaching the specific wallet instance on a user's device. E2E Encryption ensures that credential data and personally identifiable information (PII) remain confidential and unreadable to intermediary backend servers, even as they facilitate the credential delivery process. The credentials are encrypted end-to-end, from the issuer directly to the specific wallet instance, ensuring that only the intended recipient can decrypt and access the credential data. To understand where End-to-End Encryption fits within the credential issuance process, consider the standard OID4VCI workflow: 1. The wallet obtains an Authorization Code (either after the user authenticates with the configured authentication provider in an Authorization Code flow, or directly from the credential offer in a Pre-authorized Code flow). 2. The wallet makes a request to a Token endpoint to exchange the Authorization Code for a Token. 3. The wallet makes a request to the credential endpoint to issue the credential using the Token. 4. The MATTR VII tenant responds with the issued credential. With End-to-End Encryption enabled, both the credential request (step 3) and the credential response (step 4) can be encrypted. Request encryption and response encryption are independent, allowing implementations to encrypt just the request, just the response, or both. When both the request and response are encrypted, this provides complete End-to-End (E2E) Encryption throughout the credential issuance process. ## How it works [#how-it-works] End-to-End Encryption operates through a policy-based system that can be configured on a per-issuer, per-client (wallet app) basis. This flexible approach allows issuers to set specific encryption requirements for different wallet applications based on their security and privacy needs: * **Encryption Policies**: Configure encryption requirements for both credential requests and credential responses independently, providing granular control over data protection throughout the issuance workflow. * **Request Encryption Policy**: Determine whether wallet applications must encrypt their credential requests (Required or Optional), ensuring sensitive information in the request is protected in transit. * **Response Encryption Policy**: Determine whether credential responses must be encrypted back to the wallet instance (Required or Optional), protecting issued credentials and PII from intermediary visibility. * **OID4VCI Compliance**: Encryption policies can be declared through the standard `.well-known/openid-credential-issuer` endpoint to comply with OID4VCI-compliant wallet applications. However, per-client policies configured in MATTR VII take precedence over metadata declarations. * **HPKE Support**: MATTR VII currently supports Hybrid Public Key Encryption (HPKE) for both request and response encryption, providing modern, standards-based cryptographic protection. This release focuses on HPKE-7. Support for additional algorithms, such as HPKE-0, is planned for future updates to broaden interoperability and flexibility. ## Setting encryption policies [#setting-encryption-policies] Encryption policies are established for trusted wallet applications. Each policy defines the encryption requirements for that specific wallet client's interaction with the issuer: * **Request Encryption Policy**: Whether the wallet client must encrypt credential requests sent to the MATTR VII tenant: * **Required**: Wallet must encrypt all credential requests. MATTR VII will reject unencrypted requests from this wallet. * **Optional**: Wallet may send encrypted or unencrypted requests. MATTR VII will accept both. * **Response Encryption Policy**: Whether MATTR VII must encrypt credential responses to this wallet: * **Required**: MATTR VII will encrypt all credential responses to this wallet. * **Optional**: MATTR VII will encrypt credential responses based on wallet request. If no encryption details are provided, the response will be unencrypted. ## Credential request encryption [#credential-request-encryption] When a wallet application sends an encrypted credential request, MATTR VII will decrypt it as part of the issuance flow. The system enforces the configured encryption policies and handles various scenarios: ### Encrypting the request [#encrypting-the-request] To encrypt the request, follow the encryption process defined in [HPKE with JWE](https://www.ietf.org/archive/id/draft-ietf-jose-hpke-encrypt-15.html#name-message-encryption). The following simplified steps are derived from Section 7.1 of the specification, focusing on Integrated Encryption Mode with HPKE-7 and encoded with JWE Compact Serialization: 1. Retrieve the credential request encryption key from the [well-known credential issuer metadata endpoint](#credential-request-encryption-metadata). 2. Establish the HPKE sender context: * The recipient public key should be the public key extracted from the credential issuer metadata in the previous step. * No `info` is required. 3. Construct the `credential_request` as a JSON object: ```jsx title="Credential Request" { "format": "mso_mdoc", "doctype": "org.iso.18013.5.1.mDL2", "credential_response_encryption": { "jwk": { "kid": "yCnfbmYMZcWrKDt_DjNebRCB1vxVoqv4umJ4WK8RYjk", "kty": "EC", "use": "enc", "alg": "HPKE-7", "crv": "P-256", "x": "gixQJ0qg4Ag-6HSMaIEDL_zbDhoXavMyKlmdn__AQVE", "y": "ZxTgRLWaKONCL_GbZKLNPsW9EW6nBsN4AwQGEFAFFbM" }, "enc": "A256GCM" }, "proof": { "proof_type": "jwt", "jwt": "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJrdHkiOiJFQyIsIngiOiJZY1pHUXV6X0ZkQVFuTGM5MDY1RWJDM29PQ3dZd0lWZ0hSUFFSX0R3aXVNIiwieSI6InVqcDg4ZTBjWEo0SkZjeWRCZEt6clFaazM1X2puOWVjanhaTTdkbFpleU0iLCJjcnYiOiJQLTI1NiJ9fQ.eyJqdGkiOiJiN2NmMjcxZC0yMmFiLTRlN2ItYjM5MS1mOGM0YmM4MmFkM2EiLCJpc3MiOiJtb2JpbGV3YWxsZXQiLCJhdWQiOiJodHRwczovL2VuZy1jeS52aWkuYXUzMDEubWF0dHJsYWJzLmlvIiwiaWF0IjoxNzY1NzQ1NTU5fQ.tyNqDw7rkOmRrOeBEDYdeMqOlKq2VrkcvKLAH_hYCWtKHBwXO631AHBGMCOroh8ZYc2kOFp1THWsEIBYExK6EQ" } } ``` 3. Construct the `jwe_protect_header` as a JSON object: ```jsx title="JWE Protected Header" { "alg": "HPKE-7", // HPKE base mode "kid": RECIPIENT_PUBLIC_JWK.kid, }; ``` 4. Compute `encoded_jwe_protect_header` as `base64url(UTF8(JSON.stringify(jwe_protect_header)))`. 5. Encrypt HPKE with sender context: * `plaintext` should be the binary encoded `credential_request` created above. * Additional data (`aad`) should be the `UTF8(encoded_jwe_protect_header)`. 6. Construct the JWE as a JWE Compact serialization format: ```jsx title="JWE Compact Serialization" {encoded_jwe_protect_header}.{encoded_encrypted_key}.{encoded_iv}.{encoded_ciphertext}.{encoded_tag} ``` * `encoded_encrypted_key` should be the base64Url encoded encapsulated key from the sender context. * `encoded_ciphertext` should be the base64url encoded encrypted data from step 5. * `encoded_iv` should be an empty string. * `encoded_tag` should be an empty string. 7. Share the resulting JWE string as the value of the `credential_request` parameter when making a request to the credential endpoint. ### Credential request encryption metadata [#credential-request-encryption-metadata] The issuer's encryption capabilities are declared in the `.well-known/openid-credential-issuer` endpoint metadata. This allows wallet applications to discover the issuer's encryption support and requirements. The `credential_request_encryption` object declares the issuer's ability to receive and decrypt credential requests: ```json title="Credential request encryption metadata" { "credential_request_encryption": { "jwks": { "keys": [ { "kty": "EC", "kid": "issuer-enc-key", "use": "enc", "crv": "P-256", "alg": "HPKE-7", "x": "YO4epjifD-KWeq1sL2tNmm36BhXnkJ0He-WqMYrp9Fk", "y": "Hekpm0zfK7C-YccH5iBjcIXgf6YdUvNUac_0At55Okk" } ] }, "enc_values_supported": ["A256GCM"], "encryption_required": false } } ``` ## Credential response encryption [#credential-response-encryption] MATTR VII can encrypt credential responses to specific wallet instances using encryption keys provided by the wallet application. This follows the OID4VCI standard for credential response encryption. ### Encryption key provisioning [#encryption-key-provisioning] When encrypting credential responses, MATTR VII uses the specific encryption key provided by the individual wallet instance claiming the credential, ensuring that only that instance can decrypt and access the credential. Wallet applications provide encryption details in the credential request through the `credential_response_encryption` parameter when making a request to the credential endpoint: ```json title="Credential response encryption parameter" { "credential_response_encryption": { "jwk": { "kid": "wallet-instance-key", "kty": "EC", "use": "enc", "crv": "P-256", "alg": "HPKE-7", "x": "...", "y": "..." }, "enc": "A256GCM" } } ``` * `jwks` : Contains the recipient (wallet instance) public key for encryption, this should be a key from the wallet client * `alg` : * Must be an [HPKE-7](https://www.ietf.org/archive/id/draft-ietf-jose-hpke-encrypt-15.html#name-hpke-7), which refers to JWE Integrated Encryption with HPKE using DHKEM (P-256, HKDF-SHA256) KEM, HKDF-SHA256 KDF, and AES-256-GCM AEAD. * Must be a P-256 public key, which is required for HPKE-7. * `enc` : must be `A256GCM`, required for HPKE-7. ### Decrypting the response [#decrypting-the-response] The contents of an encrypted response will be encoded with JWE Compact Serialization format, with the media type set to `application/jwt` (instead of `application/json` used for unencrypted credential responses). To decrypt the response content, follow the decryption process defined in [HPKE with JWE](https://www.ietf.org/archive/id/draft-ietf-jose-hpke-encrypt-15.html#name-message-decryption). The following simplified steps are derived from Section 7.2 of the specification, focusing on Integrated Encryption Mode with HPKE-7 and encoded with JWE Compact Serialization: 1. Parse the JWE Compact Serialization, splitting the JWE string into its five base64url-encoded components: ```jsx title="JWE Compact Serialization parsing" {encoded_protected_header}.{encoded_encrypted_key}.{encoded_iv}.{encoded_ciphertext}.{encoded_tag} ``` 2. base64url decode and parse `encoded_protected_header` as JSON and validate the header payload. The headers should contain the following key material: * `alg` should be `HPKE-7`. * `kid` is optional depending on presence of kid in `credential_response_encryption.jwks` ```jsx title="JWE Protected Header" { "alg":"HPKE-7", "kid":"{wallet-key-identifier}" } ``` 3. Establish the HPKE recipient context: * The recipient key should be the private key of selected key from `credential_response_encryption.jwk`. * The encapsulated key should be the base64Url decoded `encoded_encrypted_key` from the JWE. * No `info` is required. 4. Decrypt HPKE with recipient context: * The ciphertext should be the base64Url decoded `encoded_ciphertext` from the JWE. * Additional data (`aad`) should be the `UTF8(encoded_protected_header)`. * `encoded_protected_header` is the **exact first segment string** from the compact JWE (before the first `.`), and **not** the decoded JSON. 5. Encode decrypted content into text and parse as JSON. The resulting JSON should contain an unencrypted credential response payload. ### Credential response encryption metadata [#credential-response-encryption-metadata] The issuer's encryption capabilities are declared in the `.well-known/openid-credential-issuer` endpoint metadata. This allows wallet applications to discover the issuer's encryption support and requirements. The `credential_response_encryption` object declares the issuer's ability to encrypt credential responses: ```json title ="Credential response encryption metadata" { "credential_response_encryption": { "alg_values_supported": ["HPKE-7"], "enc_values_supported": ["A256GCM"], "encryption_required": false } } ``` Currently the `encryption_required` flag is always set to `false`. Encryption requirements are enforced through the per-client policies configured for each wallet application, which override this default value where applicable. ## Security considerations [#security-considerations] When implementing End-to-End Encryption for credential issuance, consider the following security aspects: * **Key Management**: Wallet instances must securely generate and manage their encryption keys. These keys should be protected at the device level and never shared with backend servers. * **Policy Enforcement**: Encryption policies should be set based on the wallet application's architecture and trust requirements. Wallets that use proxy servers for credential delivery are prime candidates for required encryption policies. * **Encryption Algorithm**: MATTR VII currently supports the [HPKE-7 JWE algorithm](https://www.ietf.org/archive/id/draft-ietf-jose-hpke-encrypt-15.html#name-hpke-7). * This algorithm provides integrated encryption with HPKE using: * DHKEM (P-256, HKDF-SHA256) as the Key Encapsulation Mechanism (KEM) * HKDF-SHA256 as the Key Derivation Function (KDF) * AES-256-GCM as the Authenticated Encryption with Associated Data (AEAD) * This combination offers modern, secure cryptographic protection. * More algorithms will be supported in future releases. * **Wallet Identification**: Only identified and approved wallet clients can participate in encrypted issuance flows. Ensure wallet clients are properly registered and configured with the appropriate policies. Refer to the [OID4VCI security considerations](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-application-layer-encryptio) for more details. # Credential issuance URL: /docs/issuance/credential-issuance/overview ## Overview [#overview] When a user scans a credential offer QR code or follows a credential offer deeplink, it triggers the credential issuance flow. This flow enables wallets to securely claim and receive verifiable credentials from MATTR VII issuer tenants. ## Issuance Flow [#issuance-flow] The credential issuance flow is based on the [OpenID for Verifiable Credential Issuance (OID4VCI)](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) specification. MATTR VII supports both the Pre-authorized Code and Authorization Code flows. ### Pre-authorized Code Flow [#pre-authorized-code-flow] In the Pre-authorized Code flow, the authorization code is embedded directly in the credential offer, allowing immediate credential issuance without additional user authentication: 1. As the user scans the QR code or follows the deeplink, a wallet application registered to handle that URL is invoked. 2. The wallet extracts the issuer metadata endpoint URL from the credential offer and queries it to discover the issuer's configuration, including the [token](/docs/api-reference/platform/credential-issuance/postOauthToken) and [credential](/docs/api-reference/platform/credential-issuance/postOpenIdCredential) endpoint URLs. 3. The wallet uses the authorization code found in the credential offer to make a request to the [token](/docs/api-reference/platform/credential-issuance/postOauthToken) endpoint and exchange the code for an access token. 4. The wallet uses the access token returned by the token endpoint to request credential issuance from the [credential](/docs/api-reference/platform/credential-issuance/postOpenIdCredential) endpoint. 5. The credential endpoint creates the credential based on the credential configuration defined in the credential offer and responds with the signed credential. ### Authorization Code Flow [#authorization-code-flow] In the Authorization Code flow, the user must authenticate with a configured authentication provider before receiving the authorization code: 1. As the user scans the QR code or follows the deeplink, a wallet application registered to handle that URL is invoked. 2. The wallet extracts the issuer metadata endpoint URL from the credential offer and queries it to discover the issuer's configuration, including the [authorize](/docs/api-reference/platform/credential-issuance/getOauthAuthorize), [token](/docs/api-reference/platform/credential-issuance/postOauthToken), and [credential](/docs/api-reference/platform/credential-issuance/postOpenIdCredential) endpoint URLs. 3. The wallet calls the [authorize](/docs/api-reference/platform/credential-issuance/getOauthAuthorize) endpoint, which redirects the user to the configured authentication provider in a webview. Note that only registered wallets can call this endpoint and initiate the authentication process. 4. After completing authentication, the user is redirected by the authentication provider back to the redirect URI configured when making the request to the authorize endpoint. This redirect URI is a path in the wallet that can extract the authorization code from the response returned by the authentication provider following successful authentication. 5. The wallet uses the authorization code to make a request to the [token](/docs/api-reference/platform/credential-issuance/postOauthToken) endpoint and exchange the code for an access token. 6. The wallet uses the access token returned by the token endpoint to request credential issuance from the [credential](/docs/api-reference/platform/credential-issuance/postOpenIdCredential) endpoint. 7. The credential endpoint creates the credential based on the credential configuration defined in the credential offer and responds with the signed credential. ### Flow Diagram [#flow-diagram] The following diagram illustrates both the Pre-authorized Code flow and Authorization Code flow: ## Implementation Options [#implementation-options] ### Using MATTR Holding Capabilities [#using-mattr-holding-capabilities] When using MATTR's holding capabilities, this entire interaction is orchestrated by our [holder SDKs](/docs/holding/sdk-overview). The SDKs handle the complex protocol flows automatically, managing authorization, token exchange, and credential retrieval on behalf of your wallet application. ### Building Your Own Wallet [#building-your-own-wallet] Customers building their own wallet implementations can still claim credentials from MATTR VII issuer tenants by calling the OID4VCI endpoints directly. This approach gives you full control over the wallet experience while maintaining interoperability with MATTR's issuing infrastructure. ### Encryption [#encryption] In some credential issuance scenarios, a wallet application's backend server acts as a proxy or middleman between the credential issuer (MATTR VII) and individual wallet app instances. In these architectures, the credential response passes through the wallet provider's backend infrastructure before reaching the specific wallet instance on a user's device. To ensure the security and privacy of credentials in such scenarios, MATTR VII supports End-to-End Encryption (E2E Encryption) for credential issuance. This means that both the request to issue a credential and the response which includes the issued credentials can be encrypted in a way that only the intended recipient can decrypt and access them, even if they pass through intermediary servers. For more information, see [End-to-End Encryption](/docs/issuance/credential-issuance/e2e-encryption). # Wallet Attestation URL: /docs/issuance/credential-issuance/wallet-attestation ## Overview [#overview] **Issue to the wallets you recognise. Prevent the ones you don't.** Wallet Attestation gives you confidence that your credentials are only issued to trusted, verified wallet applications. It lets a wallet cryptographically prove its authenticity before claiming credentials, so you can keep your credentials out of unknown, malicious, compromised, or non-compliant wallet environments. As an issuer, you maintain a curated list of approved wallets — anchored by the Wallet Attestation root certificates their providers share with you — that are permitted to claim your credentials. This gives you stronger security, clearer compliance alignment, and control over how your credentials are distributed across ecosystems. It adds no friction for end users: attestation is validated automatically in the background as part of the standard credential retrieval flow, so issuance looks and feels the same for the people using the wallet. This mechanism is particularly valuable when: * **Enforcing wallet policies**: Ensuring that only authorized and trusted wallet applications can receive credentials from your issuer. * **Protecting against unauthorized access**: Preventing credential theft by verifying the wallet's identity before issuance. * **Supporting platform-based attestation**: Leveraging platform-specific security mechanisms (such as mobile app attestation) while presenting them in a standardized format to the issuer. The MATTR Pi Holder SDKs support Wallet Attestation automatically when [SDK Tethering](/docs/holding/sdk-operations/sdk-tethering) is configured. For holder-side implementation details, see [Wallet Attestation](/docs/holding/credential-claiming-guides/wallet-attestation) in the Holding documentation. ### What this means for your team [#what-this-means-for-your-team] Wallet Attestation touches more than just your integration code: * **For policy makers**: Define which wallet types can receive specific credentials by setting rules for approved wallet providers and applications. This increases assurance that credentials are only issued to trusted wallets, reducing exposure to unknown, malicious, compromised, or non-compliant wallet environments. * **For product managers and designers**: Your existing issuance flows and user experiences stay the same. You gain stronger assurance and security by issuing only to recognised, verified wallet applications, with attestation proofs securely shared and onboarded between you and each trusted wallet. * **For solution architects**: Configure your trusted wallet list and onboard each wallet's root certificate as a trust anchor. Design for secure key and certificate management, high availability, observability, error handling, and continuous monitoring of attestation verification outcomes and operational trust signals. ## Standards and specifications [#standards-and-specifications] Wallet Attestation in MATTR VII is based on the following standards: * [OAuth 2.0 Attestation-Based Client Authentication](https://datatracker.ietf.org/doc/draft-ietf-oauth-attestation-based-client-auth/) - The core specification for Wallet Attestation * [RFC 9449: DPoP (Demonstrating Proof-of-Possession)](https://www.rfc-editor.org/rfc/rfc9449.html) - DPoP specification * DPoP Optimization (Combined Mode) - Based on proposed improvements to the attestation specification ## How it works [#how-it-works] At a high level, Wallet Attestation establishes trust between you and a wallet application through four steps: 1. **Share and onboard the root certificate**: The wallet app provider creates a Wallet Attestation root CA certificate on their MATTR VII tenant and shares it with you. It is then onboarded as the trust anchor for that wallet application — currently through an out-of-band process with MATTR (see [Setting up Wallet Attestation](#setting-up-wallet-attestation)). 2. **Configure and operate**: You configure your issuance flows to require Wallet Attestation for that wallet. Today this is managed through an out-of-band process with MATTR; APIs for wallet trust configuration are planned for future releases. 3. **Retrieve a credential as usual**: When a user retrieves a credential, the wallet app automatically obtains or refreshes a Wallet Attestation proof — bound to that specific wallet instance — and sends it to you as part of the standard credential retrieval flow. 4. **Verify and issue**: You validate the proof against the onboarded root CA certificate. If the proof is valid and the wallet is approved, the credential is issued; otherwise an error is returned to the wallet. High-level overview of the Wallet Attestation flow between the holder's MATTR VII tenant, the wallet app, and the issuer's MATTR VII tenant The rest of this section explains the underlying trust model and protocol in detail. Wallet Attestation operates through a certificate-based trust chain between three parties: 1. **Client Attester (Wallet Backend)**: Manages certificates and issues attestation JWTs to trusted wallet instances. 2. **Client Instance (Wallet Application)**: The individual wallet app on a user's device that requests and claims credentials, holds a key pair, and shares its public key so that MATTR VII can validate proof of possession. 3. **MATTR VII (Credential Issuer)**: Validates the attestation and issues credentials only to trusted wallet instances. If validation fails, MATTR VII does not issue a credential and returns an error to the wallet. The following diagram illustrates how the three participants interact across the different attestation modes: The Wallet Attestation flow begins when a wallet instance attests its own identity to its backend—typically by presenting key information or a unique instance identifier. If the backend is satisfied with this attestation, it issues an attestation JWT that cryptographically verifies the wallet's authenticity. The wallet then presents this attestation, along with proof that it controls the associated cryptographic keys, to MATTR VII. MATTR VII validates the entire chain of trust—from the attestation certificate back to a configured root certificate—before allowing credential issuance to proceed. The MATTR Pi Holder SDK currently only supports **Combined Mode** (with DPoP). If your issuer tenant is configured to accept credentials from MATTR Pi Holder SDK wallets, ensure that Combined Mode is enabled. ### Understanding the flow [#understanding-the-flow] The attestation process involves three stages: **Attestation issuance** The wallet backend creates an Attestation JWT that includes a certificate chain in its header. This chain must trace back to one of the root certificates that MATTR VII has been configured to trust for this wallet application. The wallet backend signs this JWT with the private key corresponding to the first certificate in the chain and returns it to the wallet instance. **Token request with proof of possession** The wallet instance must prove it controls the private key associated with the public key embedded in the Attestation JWT. How this proof is provided depends on which mode is used: * **Standard Mode**: The wallet generates a separate Attestation PoP (Proof of Possession) signed with its instance key and sends both the Attestation JWT and PoP to MATTR VII. This returns a standard Bearer access token. * **Combined Mode** (recommended): When using DPoP for token binding, the wallet generates a single DPoP proof that serves as both the attestation proof and the token binding. This reduces complexity by eliminating the need for a separate Attestation PoP. MATTR VII returns a DPoP-bound access token (and optionally a refresh token). * **Standard Mode with DPoP**: The wallet provides both an Attestation PoP and a DPoP proof, resulting in a DPoP-bound access token with additional PoP binding for refresh tokens. MATTR VII validates the entire attestation—verifying the certificate chain, checking signatures, and confirming proof of possession—before issuing the access token. **Credential issuance** The wallet uses the validated access token to request credentials from MATTR VII's credential endpoint. Only after successful attestation validation does MATTR VII issue the credential. ### Certificate trust chain [#certificate-trust-chain] The trust model relies on certificates: 1. **Root Certificates**: The wallet backend establishes root certificates that MATTR VII must trust. Currently these root certificates are shared with MATTR through an out-of-band configuration process. 2. **Certificate Chain**: When the wallet backend creates an Attestation JWT, it includes a certificate chain of end-entity (and possibly intermediate certificates) in the `x5c` header. This chain must verify back to one of the configured root certificates. 3. **Signature Verification**: MATTR VII validates that the Attestation JWT is signed by the private key corresponding to the first certificate included in the `x5c` certificate chain, and that the chain verifies to a configured root certificate. ## Setting up Wallet Attestation [#setting-up-wallet-attestation] To enable Wallet Attestation for your issuer tenant, you need to: 1. **Configure trusted wallet applications**: Configure which wallet applications are trusted by your tenant. This includes configuring: * The wallet's client ID * Root certificates that the wallet backend uses * Whether DPoP is required for this wallet 2. **Share root certificates**: Provide the root certificates from your wallet backend's certificate infrastructure to MATTR. 3. **Configure attestation requirements**: Determine whether Wallet Attestation is required or optional for each wallet application. Currently, Wallet Attestation configuration is managed through out-of-band processes. APIs for wallet trust configuration are planned for future releases. ## Attestation proofs [#attestation-proofs] When requesting tokens from MATTR VII, the wallet must include attestation proofs in the request headers. The proofs consist of: * **Attestation JWT**: A JWT issued by the wallet backend that attests to the wallet instance's identity and key possession. * **Attestation DPoP**: A proof that the wallet instance possesses the private key corresponding to the public key in the Attestation JWT. Three modes of Wallet Attestation are available, based on what the wallet provides: | Mode | What the wallet sends | When to use | | --------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | **Standard Mode** | `Attestation JWT` + `Attestation PoP` | When the wallet does not use DPoP | | **Combined Mode** | `Attestation JWT` + `DPoP` | When the wallet uses DPoP for token binding. The DPoP proof serves as both the attestation proof and the token binding proof. | | **Standard Mode with DPoP** | `Attestation JWT` + `Attestation PoP` + `DPoP` | When the wallet uses DPoP for token binding but provides separate attestation and DPoP proofs. | **Combined Mode** is the recommended approach when using DPoP, as it reduces the number of proofs the wallet must generate and include in requests. ### Attestation JWT [#attestation-jwt] The Attestation JWT is issued by the wallet backend (Client Attester) and attests that a specific wallet instance possesses a particular cryptographic key. #### Structure [#structure] ```json title="Header" { "alg": "ES256", "typ": "oauth-client-attestation+jwt", "x5c": [ "MIIC3zCCAcWgAwIBAgIUJ9R0z5r3e7...base64-cert-leaf...", "MIIDdzCCAl+gAwIBAgIUFp93k2m9a8...base64-intermediate..." ] } ``` * `x5c`: Certificate chain that must verify back to a configured root certificate for the requested `client_id`. ```json title="Payload" { "sub": "example-wallet-client-id", "iat": 1300814780, "exp": 1300819380, "client_instance_id": "unique-instance-identifier", "cnf": { "jwk": { "kty": "EC", "use": "sig", "crv": "P-256", "x": "18wHLeIgW9wVN6VD1Txgpqy2LszYkMf6J8njVAibvhM", "y": "-V4dS4UaLMgP_4fY4j8ir7cl1TXlFdAgcx55o7TkcSA" } } } ``` * `sub`: The wallet client ID * `iat`, `exp`: Standard JWT time claims for issued at and expiration * `client_instance_id`: Optional unique identifier for a specific wallet instance * `cnf.jwk`: The public key of the Client Instance Key Pair. Used to verify the signature of DPoP proofs. #### How MATTR VII validates it [#how-mattr-vii-validates-it] MATTR VII performs the following validations on the Attestation JWT: 1. **Signature verification**: The JWT must be signed by the key from the first certificate in the `x5c` array 2. **Certificate chain validation**: The certificate chain in `x5c` must verify up to one of the root certificates configured for this wallet 3. **Time validation**: Standard JWT time claim validation (`iat`, `exp`) 4. **Client matching**: The `sub` claim must match the client ID making the request ### Attestation PoP [#attestation-pop] The Attestation PoP (Proof of Possession) proves that the wallet instance actually possesses the private key corresponding to the public key in the Attestation JWT's `cnf` claim. #### Structure [#structure-1] ```json title="Header" { "alg": "ES256", "typ": "oauth-client-attestation-pop+jwt" } ``` ```json title="Payload" { "aud": "https://your-tenant.vii.mattr.global", "iat": 1300815780, "jti": "d25d00ab-552b-46fc-ae19-98f440f25064" } ``` * `aud`: The MATTR VII tenant URL * `jti`: Unique identifier for this proof (prevents replay attacks) * `iat`: Time issued #### How MATTR VII validates it [#how-mattr-vii-validates-it-1] 1. **Signature verification**: The PoP must be signed with the private key corresponding to the public key in the Attestation JWT's `cnf.jwk` 2. **Time validation**: Standard JWT time claim validation 3. **Replay detection**: The `jti` is checked to ensure this PoP has not been used before Attestation PoP proofs cannot be reused. Each request must include a fresh Attestation PoP with a unique `jti`. ### Demonstrating Proof-of-Possession (DPoP) [#demonstrating-proof-of-possession-dpop] When a wallet uses [DPoP (Demonstrating Proof-of-Possession)](https://www.rfc-editor.org/rfc/rfc9449) for token binding, it can leverage **Combined Mode** to simplify attestation. In this mode: * The wallet generates its DPoP proof using the same key pair referenced in the Attestation JWT's `cnf.jwk` * The DPoP proof serves dual purposes: * Proving possession of the attestation key * Binding tokens to the wallet * No separate Attestation PoP is needed, reducing complexity #### Structure [#structure-2] ```json title="Header" { "alg": "ES256", "typ": "dpop+jwt", "jwk": { "kty": "EC", "x": "YcZGQuz_FdAQnLc9065EbC3oOCwYwIVgHRPQR_DwiuM", "y": "ujp88e0cXJ4JFcydBdKzrQZk35_jn9ecjxZM7dlZeyM", "crv": "P-256" } } ``` ```json title="Payload" { "htu": "https://your-tenant.vii.mattr.global/v1/oauth/token", "htm": "POST", "jti": "4441dede-e4a8-420a-95ee-2f6716bb384d", "iat": 1764210264, "ath": "uU0nuZNNPgilLlLX2n2r-sSE7-N6U4DukIj3rOLvzek", "htcd": "sha-256=:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=:" } ``` * `htu`: The HTTP URL of the token endpoint being called * `htm`: The HTTP method (e.g., `POST`) * `jti`: Unique identifier for this proof (prevents replay attacks) * `iat`: Time issued * `ath`: Optional base64url-encoded SHA-256 hash of the `access_token`. Required when using the access token with resource servers; optional for initial token requests. * `htcd`: Optional base64-encoded SHA-256 hash (content digest) of the HTTP request payload used to validate integrity. #### How MATTR VII validates it in Combined Mode [#how-mattr-vii-validates-it-in-combined-mode] When using Combined Mode, MATTR VII: 1. Validates the Attestation JWT as described above 2. Validates that the public key in the DPoP proof's `jwk` header matches the public key in the Attestation JWT's `cnf.jwk` 3. Validates the DPoP proof signature 4. Performs standard DPoP validations (URL, method, time, replay detection and optionally request body integrity) {/* To be added when CRUD endpoints are available for wallet trust configuration: #### Enforcing DPoP If your wallet application is configured to require DPoP, the wallet must include DPoP proofs in its requests: - **Attestation JWT + Attestation PoP** (without DPoP): Returns an error (DPoP is required but not provided) - **Attestation JWT + DPoP**: Accepted (Combined Mode - DPoP serves as both attestation proof and token binding) - **Attestation JWT + DPoP + Attestation PoP**: Accepted (Standard Mode with DPoP - uses separate attestation and DPoP proofs) */} ## Making token requests with Wallet Attestation [#making-token-requests-with-wallet-attestation] Wallets configured to require Wallet Attestation must include the attestation proofs as HTTP headers when calling the [Token](/docs/issuance/credential-issuance/api-reference#exchange-authorization-code-for-access-token) endpoint. ### Request headers [#request-headers] Depending on the authentication mode: ```http title="Standard Mode (no DPoP)" POST /v1/oauth/token HTTP/1.1 Host: your-tenant.vii.mattr.global Content-Type: application/x-www-form-urlencoded OAuth-Client-Attestation: eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWNsaWVudC1hdHRlc3RhdGlvbitqd3QiLCJ4NWMiOlsuLi5dfQ... OAuth-Client-Attestation-PoP: eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWNsaWVudC1hdHRlc3RhdGlvbi1wb3Arand0In0... grant_type=authorization_code&code=... ``` ```http title="Combined Mode (with DPoP)" POST /v1/oauth/token HTTP/1.1 Host: your-tenant.vii.mattr.global Content-Type: application/x-www-form-urlencoded OAuth-Client-Attestation: eyJhbGciOiJFUzI1NiIsInR5cCI6Im9hdXRoLWNsaWVudC1hdHRlc3RhdGlvbitqd3QiLCJ4NWMiOlsuLi5dfQ... DPoP: eyJhbGciOiJFUzI1NiIsInR5cCI6ImRwb3Arand0IiwiandrIjp7Imt0eSI6IkVDIiwieCI6Ii4uLiIsInkiOiIuLi4ifX0... grant_type=authorization_code&code=... ``` ### Response [#response] If the attestation is valid, MATTR VII returns an access token: ```json title="Successful response" { "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImF0K2p3dCJ9...", "token_type": "DPoP", "expires_in": 14400 } ``` If attestation is invalid or missing when required, an error is returned: ```json title="Error response" { "error": "invalid_client", "error_description": "Client attestation validation failed" } ``` ## Client instance identification [#client-instance-identification] The `client_instance_id` claim in the Attestation JWT enables wallet applications to include a unique identifier for the specific wallet instance requesting tokens. This identifier is then included in the issued JWT access token. ### Why use client instance identification [#why-use-client-instance-identification] By including a unique `client_instance_id` in the Attestation JWT, the wallet can: * **Enable instance tracking**: Maintain a correlation between specific wallet instances and their issued credentials * **Enable instance-specific credential data**: MATTR VII can use the `client_instance_id` when querying configured claims sources, allowing issuers to retrieve instance-specific data from existing data sources for more refined and personalized issuance journeys This mechanism is particularly valuable in environments where: * Multiple wallet instances operate simultaneously (e.g., on different devices). * Additional verification of the token issuance flow is required. * Issuers need to customize credential content based on the specific device or application instance receiving the credential. ### How it works [#how-it-works-1] The `client_instance_id` flow operates as follows: 1. **Unique identifier generated**: A unique identifier for a specific wallet instance is generated (e.g., a UUID or device-specific identifier) 2. **Include in Attestation JWT**: The wallet backend includes this identifier in the `client_instance_id` claim when creating the Attestation JWT 3. **MATTR VII extracts and uses the identifier**: When processing the token request, MATTR VII: * Extracts the `client_instance_id` from the Attestation JWT * Includes it in the issued JWT access token * Can use it when querying configured claims sources to retrieve instance-specific credential data By making the `client_instance_id` available during credential issuance, MATTR VII enables issuers to tailor credential content based on the specific wallet instance. For example, claims sources can return different data sets, permissions, or attributes based on which device or application instance is receiving the credential, supporting use cases like device-specific entitlements or instance-aware access control. The following diagram shows how the instance identifier flows through the attestation and token issuance process: **How the identifier flows:** 1. **Generation**: The wallet instance creates or retrieves a unique identifier for itself (typically a UUID or device-specific identifier). 2. **Embedding in attestation**: When requesting an Attestation JWT from the wallet backend, the instance provides this identifier. The wallet backend embeds it in the `client_instance_id` claim of the Attestation JWT. 3. **Extraction and propagation**: When the wallet requests an access token from MATTR VII, MATTR extracts the `client_instance_id` from the Attestation JWT and includes it in the issued JWT access token. 4. **Use during issuance**: When the wallet subsequently requests credentials, MATTR VII can use the `client_instance_id` from the access token to query claims sources with instance-specific parameters, enabling personalized credential data based on the specific device or application instance. ### Technical details [#technical-details] The `client_instance_id` claim is optional in the Attestation JWT. When present: * The **Attestation JWT payload** includes the following claims: * `sub`: The subject of the Attestation JWT (for example, the wallet client application). * `exp`: Expiration time of the JWT. * `client_instance_id`: A unique identifier for the specific wallet instance (for example, a UUID). * `cnf.jwk`: The public key material that the wallet proves possession of when requesting tokens. ```json title="Attestation JWT payload" { "sub": "example-wallet-client-id", "client_instance_id": "550e8400-e29b-41d4-a716-446655440000", "exp": 1300819380, "iat": 1300814780, "cnf": { "jwk": { "kty": "EC", "crv": "P-256", "x": "base64url-encoded-x-coordinate", "y": "base64url-encoded-y-coordinate", "kid": "wallet-key-id" } } } ``` * And the **JWT access token** will contain the same value: ```json title="JWT Access Token payload" { "sub": "user-subject-identifier", "client_id": "example-wallet-client-id", "client_instance_id": "550e8400-e29b-41d4-a716-446655440000", "exp": 1300819380, "iat": 1300814780 } ``` * **JWT access tokens only**: The `client_instance_id` is included only in JWT-format access tokens. JWT access tokens are issued when credential refresh (refresh tokens) is enabled for the tenant; if your tenant uses opaque access tokens (no credential refresh), this claim will not be present. ### Requirements and scope [#requirements-and-scope] Client instance identification is available under the following conditions: * This feature applies only to the [Authorization Code flow](/docs/issuance/authorization-code/overview) and is **not supported** for the [Pre-authorized Code](/docs/issuance/pre-authorized-code/overview) flow. * **JWT access tokens only**: The `client_instance_id` is included only in JWT-format access tokens (when credential refresh is enabled for the tenant), not in opaque tokens. * **Wallet Attestation enabled**: The wallet must be configured for Wallet Attestation with the appropriate feature flags and configuration. ### Using client instance ID with claims sources [#using-client-instance-id-with-claims-sources] When the `client_instance_id` is provided through Wallet Attestation, MATTR VII makes it available for use in [Claims source](/docs/issuance/claims-source/overview) requests during credential issuance. This enables issuers to retrieve instance-specific data from external systems based on which specific wallet instance is claiming the credential and supports several use cases: * **Device-specific entitlements**: Return different credential claims or permissions based on which device is receiving the credential * **Instance-aware data filtering**: Filter or customize credential data based on the specific wallet instance * **Audit and tracking**: Track which wallet instances have claimed credentials for compliance or security monitoring * **Multi-device management**: Manage credentials across multiple devices belonging to the same user, with instance-specific attributes During the credential endpoint request, MATTR VII queries configured claims sources to retrieve additional credential data. When Wallet Attestation is in use and a `client_instance_id` is present in the access token, MATTR VII includes this information in the data available for request parameter mapping: ```json title="Data available for claims source request mapping" { "authenticationProvider": { "url": "https://auth-provider.example.com", "subject": "user-123" }, "claims": { // ... user persisted claims }, "credentialConfiguration": { "id": "credential-config-id", "type": "VerifiableCredential", "profile": "mso_mdoc" }, "issuanceProtocol": "openid4vci", // [!code focus] "wallet": { // [!code focus] "id": "wallet-client-id", // [!code focus] "instanceId": "550e8400-e29b-41d4-a716-446655440000" // [!code focus] }, // [!code focus] "client": { "instanceId": "550e8400-e29b-41d4-a716-446655440000" } } ``` The `client` object is retained for backwards compatibility but is being phased out. New claims source configurations should map from the `wallet` object (`wallet.id`, `wallet.instanceId`) instead of `client.instanceId`. When [creating](/docs/issuance/claims-source/api-reference#configure-a-claims-source) or configuring a claims source, you can map the `wallet.instanceId` to request parameters that will be sent to your claims source server: ```json title="Claims source configuration example" { "url": "https://claims.example.com/api/credentials", "requestParameters": { "accountType": { "mapFrom": "claims.accountType" }, "sub": { "mapFrom": "authenticationProvider.subject" }, "deviceId": { "mapFrom": "wallet.instanceId" } } } ``` With this configuration, when MATTR VII queries the claims source during credential issuance, it will send a request like: ```http GET https://claims.example.com/api/credentials?accountType=premium&sub=user-123&deviceId=550e8400-e29b-41d4-a716-446655440000 ``` The `wallet.instanceId` is only available when Wallet Attestation is enabled and the issued access token is a JWT that includes `client_instance_id`. It will not be available when opaque access tokens are used. If the `wallet.instanceId` is not available (for example, when Wallet Attestation is not in use or the access token does not include `client_instance_id`), the mapped parameter will be omitted from the claims source request. Design your claims source endpoints to handle requests both with and without the instance ID parameter, unless you exclusively use Wallet Attestation for all issuance flows. ## Refresh token binding [#refresh-token-binding] When issuing refresh tokens (in conjunction with [Credential Refresh](/docs/issuance/refresh/overview)), MATTR VII binds the refresh token to the Wallet Attestation: * **Combined Mode**: Refresh token binding is handled through DPoP binding (as DPoP serves as the attestation proof) * **Standard Mode**: Refresh token is bound to the public key in the Attestation JWT's `cnf.jwk` When the wallet uses the refresh token to obtain a new access token: * The wallet must provide the same attestation proofs (Attestation JWT + DPoP) * The public key in the new Attestation JWT must match the key from the original token request * If the keys don't match, the refresh request will be rejected This ensures that only the original wallet instance can use a refresh token, even if the refresh token itself is compromised. ## Implementation considerations [#implementation-considerations] ### Certificate management [#certificate-management] * Root certificates should be protected and rotated according to your organization's security policies * Multiple root certificates can be configured to support certificate rotation without service interruption * Expired or compromised certificates should be removed from the trusted configuration immediately ### Key management [#key-management] * Client Instance Key Pairs should be generated and stored securely on the wallet device * Private keys should never leave the device or be transmitted to any server * Consider using hardware-backed keystores or secure enclaves when available ### Replay protection [#replay-protection] * Always include a unique `jti` in Attestation PoP and DPoP proofs * MATTR VII maintains replay detection to prevent reuse of proofs * Attestation JWTs can be reused within their validity period, but Attestation PoP proofs cannot ### Time synchronization [#time-synchronization] * Ensure wallet devices have accurate time synchronization * Clock skew can cause valid attestations to be rejected * Use reasonable validity windows in `iat` and `exp` claims to account for minor time differences ### Client Instance Validation [#client-instance-validation] When implementing client instance identification: * **Unique identifiers**: Use truly unique identifiers for each wallet instance to prevent collisions * **Identifier format**: While any string value is supported, UUIDs or similar globally unique identifiers are recommended * **Security implications**: The `client_instance_id` is not encrypted in the JWT access token, so avoid including sensitive information in the identifier value # API Reference URL: /docs/issuance/credential-offer/api-reference ## Create an Authorization Code flow Credential Offer [#create-an-authorization-code-flow-credential-offer] ## Create a Pre-Authorized Code flow Credential Offer [#create-a-pre-authorized-code-flow-credential-offer] ## Delete a Pre-Authorized Code flow Credential Offer [#delete-a-pre-authorized-code-flow-credential-offer] # How to create an OID4VCI credential offer URL: /docs/issuance/credential-offer/guide To issue a credential via the [OID4VCI](/docs/issuance/oid4vci-overview) workflow you must create a [Credential offer](/docs/issuance/credential-offer/overview). This offer specifies the [Credential configurations](/docs/issuance/credential-configuration/overview) that will be used to issue the credential, as well as additional parameters to support the issuance workflow. Once the offer is created, it must be shared with the credential's intended holder so that they can claim the credential. The process is similar for both the [Authorization Code flow](/docs/issuance/authorization-code/overview) and the [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview). The main difference is the type of credential offer you need to create, depending on which workflow you are using. ## Prerequisites [#prerequisites] * The `id` identifier of one or more [Credential configurations](/docs/issuance/credential-configuration/overview) you wish to include in this offer. This is obtained when you [create a Credential configuration](/docs/issuance/credential-configuration/overview). * DIDs (Only required when [sharing the offer as a DID message](#send-an-offer-uri)): * Issuer DID: This is a [`did:web`](/docs/concepts/dids#didweb) that identifies the issuer who attests the claims in the credential are accurate. * Subject DID: This is a [`did:key`](/docs/concepts/dids#didkey) that identifies the intended holder of the credential. This DID is usually retrieved from the intended holder's digital wallet. * 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. ## Overview [#overview] The OID4VCI credential offer lifecycle comprises the following steps: 1. [Generate an offer URI](#generate-an-offer-uri). 2. [Send an offer URI](#send-an-offer-uri). 3. [Accept a Credential offer](#accept-a-credential-offer). ### Generate an offer URI [#generate-an-offer-uri] Generate the offer URI by making one of the following requests based on the issuance flow you are using: 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Credential offer**. 3. Select the **Select** button. 4. Check the checkbox next to the credential configuration you wish to include in the credential offer. 5. Select the **Apply** button. 6. Select the **Generate** button. 7. Download the generated QR code by selecting the **Download** button. Make a request of the following structure to [generate a new credential offer](/docs/issuance/authorization-code/api-reference#create-credential-offer) for an [OID4VCI Authorization Code flow](/docs/issuance/authorization-code/overview): ```http title="Request" POST /v1/openid/offers ``` ```json title="Request body" { "credentials": ["20d6bbe6-a978-447c-b5bd-f33b6dca19e2"], "request_parameters": { "login_hint": "user@example.com", "prompt": "login" } } ``` * `credentials` : This array includes a list of identifiers for credential configurations that will be included in the credential offer. These identifiers are the `id` elements returned in the response when you [create a Credential configuration](/docs/issuance/credential-configuration/overview). To issue multiple credential formats of the same credential in a single flow, include all the required credential configuration id elements in the request payload. For example, you could issue a CWT and mDocs credentials using the same data in a single user journey. * `request_parameters` (*optional*): Specifies a list of additional request parameters that are included in the credential offer and can be used by the wallet as part of the authentication workflow: * `login_hint` : Login hints are included in the authentication flow the holder is redirected to after accepting the credential offer. For example, you can include the user's e-mail so that it is already populated in the login screen. * `prompt` : Prompts are sent to the [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) to control the authentication flow. For example, using `login` would always require the user to authenticate, even if they had already completed login on the same device. *Response* ```json title="Response body" { "uri": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Ftenant.vii.mattr.global%22%2C%22credentials%22%3A%5B%2220d6bbe6-a978-447c-b5bd-f33b6dca19e2%22%5D%2C%22request_parameters%22%3A%7B%22login_hint%22%3A%22user%40example.com%22%2C%22prompt%22%3A%22login%22%7D%7D" } ``` * `uri` : This is the URI that should be used by the intended holder to claim the credential. For an Authorization Code flow credential offers this URI includes the parameters required to redirect the user to the configured Authentication provider to complete authentication before issuing the credential. When a multi-format credential offer is created, this `uri` is used to issue all the credential formats in a single workflow. The Pre-authorized Code flow is only supported for [mDocs](/docs/concepts/mdocs). Creating pre-authorized code flow credential offers in the MATTR Portal is for testing purposes only. The populated user ID is generated by the MATTR Portal and cannot be changed to another user later. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Credential offer**. 3. Use the *Workflow* radio button to select **Pre-authorized code flow**. 4. Select the **Select** button. 5. Check the checkbox next to the credential configuration you created in the previous step. 6. Select the **Apply** button. 7. Use the *Claims* panel to add any claims you wish to include in the issued credential.\ These claims must match the mapping defined in the [Credential configuration](/docs/issuance/credential-configuration/overview#claims-mapping). 8. Use the *Claims to persist* textbox to specify any claims you wish to persist in the MATTR VII database.\ By default no claims are persisted. 9. Use the *Transaction code mode* dropdown to select the desired mode for the transaction code. 10. Use the *Offer valid for* textbox to specify how long the offer will be valid for.\ By default, the offer is valid for 5 minutes, and the maximum allowed duration is 10 minutes. 11. Select the **Generate** button. 12. If a transaction code was configured, copy it from the screen.\ In production deployments you will need to provide it to the user through a different channel (e.g., email, SMS). 13. Download the displayed QR code.\ This QR code will be used by the holder to claim the credential. Make a request of the following structure to [generate a new credential offer](/docs/issuance/pre-authorized-code/api-reference#create-credential-offer) for an [OID4VCI Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview): The total size of the request cannot exceed 500KB, including any claims. This is mostly relevant when including large claims such as images. When deciding how large your claims can be, consider how the credential will be presented. If holders present the credential over Bluetooth Low Energy (BLE), large payloads such as high-resolution images can degrade the transfer experience or fail on some devices. We recommend keeping payloads as small as practical for BLE, and reserving larger payloads for remote presentation flows. See the [credential configuration best practices](/docs/issuance/credential-configuration/overview#credential-content) for more guidance on claim sizes. ```http title="Request" POST /v1/openid/offers/pre-authorized ``` ```json title="Request body" { "credentials": ["707e920a-f342-443b-ae24-6946b7b5033e"], "userId": "b7e2c8f4-1a2b-4c3d-9e5f-8a7b6c5d4e3f", "transactionCodeConfiguration": { "inputMode": "numeric", "description": "Please enter the one-time code that was sent to you via email." }, "claims": { "givenName": "John", "familyName": "Doe", "email": "john.doe@example.com", "userId": "e7b8c2f1-4a3d-4e6b-9c2a-1f5d8e7a9b3c" }, "claimsToPersist": ["userId"], "expiresIn": { "minutes": 5, "seconds": 0 } } ``` * `credentials` : This array includes a list of identifiers for mDoc credential configurations that will be included in the credential offer. These identifiers are the `id` elements returned in the response when you [create a Credential configuration](/docs/issuance/credential-configuration/overview). To issue multiple credential formats of the same credential in a single flow, include all the required credential configuration id elements in the request payload. Providing an identifier of a non-mDoc credential configuration will result in an error. * `userId` : Unique system generated identifier to reference the user for this offer. This can be obtained by [searching for a user](/docs/api-reference/platform/users/searchUsers). If not provided, a new user entity will be created. * `transactionCodeConfiguration` : This object contains the configuration for the transaction code that will be associated with this credential offer and must be provided by the holder when claiming the credential. It includes the following properties: * `inputMode` : The input mode for the transaction code. Currently only `numeric` is supported. * `description` : A description of the transaction code that will be sent to the user as part of the credential offer. * `claims` : This object contains the claims that will be included in the issued credential. * The claims must match the mapping defined in the Credential configuration. * By default, claims data is only stored for the lifetime of the offer, unless you specify them in the optional `claimsToPersist` array. * `claimsToPersist` : This array includes a list of claims that will be persisted against the [user object](/docs/issuance/users/overview) in the MATTR VII database. These claims are then available for any future credential offers or issuance operations for this user. By default no claims are persisted, and it is recommended to consider carefully which claims to persist, if any, as this has implications for data privacy and security. * `expiresIn` : Specifies when the offer will expire. Once the offer expires, the user can no longer use it to claim a credential, and a new offer must be generated. The expiration period can include any combination of `minutes` and `seconds`. By default, the offer expires in 5 minutes, and the maximum allowed duration is 10 minutes. *Response* ```json title="Response body" { "id": "b1a7c9e2-4d5f-4a6b-9e8c-2f3d4b5a6c7e", // [!code highlight] "userId": "b7e2c8f4-1a2b-4c3d-9e5f-8a7b6c5d4e3f", "uri": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fexample.com%22%2C%22credentials%22%3A%5B%222edaf985-fcc2-4448-9c8e-a04c6c7351c2%22%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22stukD6lg9c9tQ3jUCa32wVi1HI%2BQIVsFK%2FQPvC2CHRs%3D%22%2C%22tx_code%22%3A%7B%22length%22%3A6%2C%22input_mode%22%3A%22numeric%22%2C%22description%22%3A%22Please%20provide%20the%20one-time%20code%20that%20was%20sent%20via%20e-mail%22%7D%7D%7D%7D", // [!code highlight] "expiresAt": "2025-05-01T00:01:00.000Z", // [!code highlight] "transactionCode": 493536 // [!code highlight] } ``` * `id` : Unique system generated identifier to reference this offer. * `uri` : This is the URI that should be used by the intended holder to claim the credential. For a Pre-authorized Code flow credential offers this URI includes the pre-authorized code which enables the wallet to retrieve the credential without requiring the user to provide any additional authentication. * `expiresAt` : The date and time when the offer will expire. Once the offer expires, the user can no longer use it to claim a credential, and a new offer must be generated. * `transactionCode` : This is the transaction code that must be provided by the user when claiming the credential. This code must be shared by the issuer with the holder via a separate and secure channel. ### Send an offer URI [#send-an-offer-uri] Once a credential offer URI is generated, you can send it to the intended holder in one of the following methods: * Send via a QR code. * Send via a Deeplink. * Send via a DID message. Refer to [Claiming credential offers](/docs/issuance/credential-offer/overview#claiming-credential-offers) for more details on how to adjust the offer URI to use specific schemes and the resulting user experience. You can use any of these methods regardless of the issuance flow you are using (Authorization Code or Pre-authorized Code). A common way to allow a digital wallet user to claim a credential is to encode the offer URI into a QR code. You could even print out the QR code. You can use a tool similar to the following to convert the URI to a QR code (make sure you use the `Plain text` option where available): * [https://www.the-qrcode-generator.com/](https://www.the-qrcode-generator.com/) * [http://goqr.me/api/](http://goqr.me/api/) * [https://www.qr-code-generator.com/](https://www.qr-code-generator.com/) MATTR is not affiliated with any of these service providers and cannot vouch for their offerings. Once the QR code is created, you can send it to the intended holder via your preferred communication channel. **Best practices** * Make sure the QR code is large enough in size to be resolvable by a phone camera; 200px square is generally sufficient. You can enable the intended holder to open the offer URI directly from their mobile device by creating a deep link. 1. Perform base64url encoding of the offer URI. 2. Use the output (the offer URI encoded as base64url) to create a link of the following structure: `global.mattr.wallet://accept/{base64Url(openid-credential-offer://...)}` For example: `global.mattr.wallet://accept/b3BlbmlkLWNyZWRlbnRpYWwtb2ZmZXI6Ly8_Y3JlZGVudGlhbF9vZmZlcj0lN0IlMjJjcmVkZW50aWFsX2lzc3VlciUyMiUzQSUyMmh0dHBzJTNBJTJGJTJGdGVuYW50LnZpaS5tYXR0ci5nbG9iYWwlMjIlMkMlMjJjcmVkZW50aWFscyUyMiUzQSU1QiUyMjIwZDZiYmU2LWE5NzgtNDQ3Yy1iNWJkLWYzM2I2ZGNhMTllMiUyMiU1RCU3RA` 3. Once the deep link is created, you can send it to the recipient via your preferred communication channel. **MATTR GO deep-links** When sending a deep link to a [MATTR GO Hold](/docs/holding/go-hold/getting-started), replace the scheme with your app bundle ID. The following example shows a deep link for a MATTR GO wallet that has `com.example.wallet` as its app bundle ID, as shown in the example below: `com.example.wallet://accept/b3BlbmlkLWNyZWRlbnRpYWwtb2ZmZXI6Ly8_Y3JlZGVudGlhbF9vZmZlcj0lN0IlMjJjcmVkZW50aWFsX2lzc3VlciUyMiUzQSUyMmh0dHBzJTNBJTJGJTJGdGVuYW50LnZpaS5tYXR0ci5nbG9iYWwlMjIlMkMlMjJjcmVkZW50aWFscyUyMiUzQSU1QiUyMjIwZDZiYmU2LWE5NzgtNDQ3Yy1iNWJkLWYzM2I2ZGNhMTllMiUyMiU1RCU3RA` **MATTR Pi deep-links** For MATTR Pi Holder SDK customers, refer to your own implementation for deep linking requirements and best practices. To send the Credential offer as a DID message you must first encrypt it and then send it as an encrypted message. **Step 1: Encrypt a Credential offer** Make a request of the following structure to [encrypt the Credential offer](/docs/api-reference/platform/messaging/encryptMessage): ```http title="Request" POST /v1/messaging/encrypt ``` ```json title="Request body" { "senderDidUrl": "did:web:learn.vii.au01.mattr.global#z6LShWb1DVC2gkxoQ91VwHmNhci2A4NdVH4srFvLiTP6ETBK", "recipientDidUrls": [ "did:key:z6MkgmEkNM32vyFeMXcQA7AfQDznu47qHCZpy2AYH2Dtdu1d" ], "payload": { "id": "731961f2-bdc3-4f1e-8d59-cc308fd60ec8", "type": "https://mattr.global/schemas/verifiable-credential/offer/OidcCredentialProvider", "from": "did:web:organization.com", "created_time": 1616466734, "body": { "uri": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Ftenant.vii.mattr.global%22%2C%22credentials%22%3A%5B%2220d6bbe6-a978-447c-b5bd-f33b6dca19e2%22%5D%2C%22request_parameters%22%3A%7B%22login_hint%22%3A%22user%40example.com%22%2C%22prompt%22%3A%22login%22%7D%7D" } } } ``` * `senderDidUrl` : Use the [Issuer DID](#prerequisites). * `recipientDidUrls` : Use the [Subject DID](#prerequisites). * `payload` : * `id` : Use the [Credential configuration `id`](#prerequisites). * `type` : Use the `https://mattr.global/schemas/verifiable-credential/offer/OidcCredentialProvider` to indicate that this message includes a Credential offer. * `from` : Use the [Issuer DID](#prerequisites). * `created_time` : Time of creation (Epoch Unix timestamp). The value must be a number and not a string, otherwise the holder will not be able to accept the Credential offer. * `body` : * `uri` : Use the credential offer URI, obtained when the offer was [created](#generate-an-offer-uri). *Response* ```json title="Response body" { "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 Credential offer** Make a request of the following structure to [send the encrypted Credential offer](/docs/api-reference/platform/messaging/sendMessage): ```http title="Request" POST /v1/messaging/send ``` ```json title="Request body" { "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](#prerequisites). * `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). ### Accept a Credential offer [#accept-a-credential-offer] Once the Credential offer is shared with the intended holder, it is up to them to use their digital wallet to accept the offer and claim the credential. The credential is only issued after the intended holder accepts the Credential offer. # Credential offer URL: /docs/issuance/credential-offer/overview ## Overview [#overview] Once all the required workflow components are configured, you can initiate an OID4VCI workflow by creating a Credential offer. This is achieved by making an API request to a specific MATTR VII endpoint. The request defines the [credential configurations](/docs/issuance/credential-configuration/overview) that will be used as well as additional request parameters to support the issuance workflow. MATTR VII responds with an offer URI, which can be shared with intended holders as a QR code, deep-link or wallet notification. This enables the digital wallet to present the offer to the holder and request for consent to initiate the issuance workflow. MATTR VII exposes two different endpoints to create a credential offer, depending on the authentication flow you are using: * [Authorization Code flow](/docs/issuance/authorization-code/overview): The returned offer URI will be used by the wallet to redirect the holder to the configured Authentication provider for authentication. Once the holder is authenticated, they are redirected back to the issuance workflow. * [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview): The returned offer URI already includes the pre-authorized code, which is used to authenticate the holder. This code is passed to the wallet and used to request an access token from the issuer’s token endpoint. The access token is then used to request the credential from the issuance endpoint. **Claiming behavior:** * **Authorization Code flow offers** can be claimed multiple times by different users. Each user authenticates independently during the issuance workflow, allowing the same offer URI to issue credentials with different user-specific data to multiple holders. * **Pre-authorized Code flow offers** are generated for a specific user after out-of-band authentication. Once successfully claimed, the pre-authorized code is consumed and the offer becomes invalid. The offer cannot be claimed again, even if the same user attempts to claim it. The OID4VCI specification supports issuing multiple credentials in a single workflow, so you can reference multiple credential configurations in the same offer. When a multi-format credential offer is created, the generated URI offer is used to issue all the credential formats in a single workflow. ## Controlling how credentials are claimed [#controlling-how-credentials-are-claimed] When generating a credential offer, you can influence which wallet applications are able to claim the offer and how the user experience flows. This involves both user experience considerations and security, privacy, or commercial requirements where you want a specific app to claim the credential. The mechanism for controlling this is through URI schemes. When you generate a credential offer (for either Authorization Code or Pre-authorized Code flows), you apply a URI scheme that determines which apps can handle the offer and what the user experience will be. ### Understanding URI schemes [#understanding-uri-schemes] A URI scheme is the protocol part at the beginning of a URI (such as `https://`, `mailto:`, or custom schemes like `openid-credential-offer://`). The URI scheme you choose affects: * **Which apps can handle the offer** - Some schemes allow any compatible app, while others ensure only your specific app can handle the offer. * **User experience** - How smoothly users can claim credentials and whether they see app selection prompts. * **Security and control** - Your level of control over the claiming process and ability to prevent unintended apps from intercepting offers. There are three main URI scheme types available: 1. **Standard OID4VCI custom scheme** (`openid-credential-offer://`) - The baseline scheme for maximum interoperability. 2. **Private-use URI scheme** (`com.example.wallet://`) - A unique custom scheme for better targeting. 3. **Claimed HTTPS scheme** (`https://example.com/wallet/...`) - Domain-verified links for maximum security and control. ### Choosing the right URI scheme [#choosing-the-right-uri-scheme] Select a URI scheme based on your requirements: | Requirement | Recommended Scheme | | ---------------------------------------------------------- | ----------------------- | | Maximum interoperability - work with any compatible wallet | Standard OID4VCI scheme | | Target a specific wallet without domain verification | Private-use URI scheme | | Ensure only your specific app can handle offers | Claimed HTTPS scheme | | Production deployment with strict security requirements | Claimed HTTPS scheme | | Development and testing | Standard OID4VCI scheme | ### Implementing URI schemes [#implementing-uri-schemes] The following sections show you how to implement each URI scheme type as an issuer. #### Standard OID4VCI custom scheme [#standard-oid4vci-custom-scheme] The OID4VCI specification defines the `openid-credential-offer://` scheme as the baseline for credential offers. This scheme provides maximum interoperability, allowing any wallet app that supports the standard to claim your offers. **When to use:** * You want to support multiple wallet applications in your ecosystem. * Interoperability is more important than controlling which specific app handles the offer. * You're developing or testing and need a simple, standards-compliant approach. **How it works:** MATTR VII generates credential offers using this scheme by default. No additional configuration is required from the issuer. **Implementation:** 1. [Create a credential offer](/docs/issuance/credential-offer/guide#generate-an-offer-uri) using the MATTR VII API. 2. Use the returned `uri` field directly: ```json title="Authorization Code flow offer response" { "uri": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Ftenant.vii.mattr.global%22%2C%22credentials%22%3A%5B%2220d6bbe6-a978-447c-b5bd-f33b6dca19e2%22%5D%7D" } ``` ```json title="Pre-authorized Code flow offer response" { "id": "6f9d3c7e-2a14-4e5e-9c0b-6a3c4b2f9d81", "userId": "c8e7b6a2-4d3f-4e3b-9d1a-2f6b1c9e5a42", "uri": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Ftenant.vii.mattr.global%22%2C%22credentials%22%3A%5B%2220d6bbe6-a978-447c-b5bd-f33b6dca19e2%22%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22eyJhbGc...%22%7D%7D%7D", "expiresAt": "2025-05-01T00:01:00.000Z", "transactionCode": 493536 } ``` 3. Present the offer to users as a [QR code](#displayed-as-a-qr-code-cross-device) or [deep link](#displayed-as-a-hyperlink-or-button-same-device). **Trade-offs:** * ✅ Works with any compatible wallet application. * ✅ No additional implementation required. * ✅ Standards-compliant and widely supported. * ⚠️ Users may see multiple wallet options if they have several installed. * ⚠️ Cannot guarantee which specific app will handle the offer. **Requirements for wallet applications:** For wallet apps to claim these offers, they must be configured to handle the `openid-credential-offer://` scheme. See the [holder guide](/docs/holding/credential-claiming-guides/handling-uri-schemes#standard-openid4vci-custom-scheme) for implementation details. #### Private-use URI scheme [#private-use-uri-scheme] A private-use URI scheme uses reverse-domain notation (e.g., `com.example.wallet://`) to target a specific wallet application. While this doesn't prevent other apps from registering the same scheme, it makes conflicts less likely due to the unique nature of reverse-domain names. **When to use:** * You want to target a specific wallet app you control or partner with. * You need better targeting than the standard scheme but don't require the security guarantees of domain verification. * You're operating in a controlled environment where you know which apps users have installed. **How it works:** You transform the MATTR VII generated offer URI by encoding it within a custom URI scheme that your wallet app is configured to handle. **Implementation:** 1. [Create a credential offer](/docs/issuance/credential-offer/guide#generate-an-offer-uri) using the MATTR VII API. 2. Extract the `uri` field from the response. 3. Transform the URI to use your custom scheme: ```javascript title="Transform to custom scheme" // Original URI from MATTR VII const originalUri = "openid-credential-offer://?credential_offer=%7B%22credential_issuer..."; // Base64 URL encode the original URI const encodedOffer = btoa(originalUri) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); // Construct custom scheme URI const customUri = `com.example.wallet://accept/${encodedOffer}`; // Result: com.example.wallet://accept/b3BlbmlkLWNyZWRlbnRpYWwtb2ZmZXI6Ly8_Y3JlZGVudGlhbF9vZmZlcj0lN0IlMjJjcmVkZW50aWFsX2lzc3Vlci4uLg ``` Or using a query parameter: ```javascript title="Transform to custom scheme with query parameter" const customUri = `com.example.wallet://accept?offer=${encodedOffer}`; // Result: com.example.wallet://accept?offer=b3BlbmlkLWNyZWRlbnRpYWwtb2ZmZXI6Ly8_Y3JlZGVudGlhbF9vZmZlcj0lN0IlMjJjcmVkZW50aWFsX2lzc3Vlci4uLg ``` 4. Present the transformed URI to users as a [QR code](#displayed-as-a-qr-code-cross-device) or [deep link](#displayed-as-a-hyperlink-or-button-same-device). **Trade-offs:** * ✅ Better targeting of a specific wallet app. * ✅ Less likely to conflict with other apps than the standard scheme. * ✅ Relatively simple to implement. * ⚠️ Doesn't prevent other apps from registering the same scheme. * ⚠️ Requires coordination with the wallet app developer to know their custom scheme. * ⚠️ Not as secure as domain-verified HTTPS schemes. **Requirements for wallet applications:** The wallet app must: * Register to handle your custom URI scheme (e.g., `com.example.wallet://`). * Decode the base64 URL-encoded offer and extract the original OID4VCI offer URI. * Process the offer using the standard OID4VCI flow. See the [holder guide](/docs/holding/credential-claiming-guides/handling-uri-schemes#private-use-uri-scheme) for implementation details. #### Claimed HTTPS scheme (App Links / Universal Links) [#claimed-https-scheme-app-links--universal-links] HTTPS schemes use domain-verified App Links (Android) or Universal Links (iOS) to ensure that only your specific app can handle credential offers from your domain. This provides the highest level of security and control. **When to use:** * You need to guarantee that only your specific wallet app can handle offers. * You're deploying in production with strict security requirements. * You want the smoothest user experience with no app selection prompts. * You control both the issuing system and the wallet application. **How it works:** You transform the MATTR VII generated offer URI into an HTTPS URL pointing to your domain. The operating system verifies that your domain is registered to open your specific app through association files hosted on your web server. **Implementation:** **Step 1: Set up domain verification files** Host the following verification files on your web server: **For iOS (Universal Links):** Create `apple-app-site-association` (no file extension) at `https://yourdomain.com/.well-known/apple-app-site-association`: ```json title="apple-app-site-association" { "applinks": { "apps": [], "details": [ { "appID": "TEAM_ID.com.example.wallet", "paths": ["/wallet/*"] } ] } } ``` Replace `TEAM_ID` and `com.example.wallet` with your Apple Developer Team ID and the wallet app's bundle identifier. **For Android (App Links):** Create `assetlinks.json` at `https://yourdomain.com/.well-known/assetlinks.json`: ```json title="assetlinks.json" [ { "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.example.wallet", "sha256_cert_fingerprints": [ "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99" ] } } ] ``` Replace `com.example.wallet` and the fingerprint with your wallet app's package name and SHA-256 certificate fingerprint. Ensure both files: * Are served with `Content-Type: application/json`. * Are accessible via HTTPS without redirects. * Have correct permissions (readable by web servers). **Step 2: Transform credential offers** 1. [Create a credential offer](/docs/issuance/credential-offer/guide#generate-an-offer-uri) using the MATTR VII API. 2. Extract the `uri` field from the response. 3. Transform the URI to use your HTTPS domain: ```javascript title="Transform to HTTPS scheme" // Original URI from MATTR VII const originalUri = "openid-credential-offer://?credential_offer=%7B%22credential_issuer..."; // Base64 URL encode the original URI const encodedOffer = btoa(originalUri) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); // Construct HTTPS URI const httpsUri = `https://yourdomain.com/wallet/accept?offer=${encodedOffer}`; // Result: https://yourdomain.com/wallet/accept?offer=b3BlbmlkLWNyZWRlbnRpYWwtb2ZmZXI6Ly8_Y3JlZGVudGlhbF9vZmZlcj0lN0IlMjJjcmVkZW50aWFsX2lzc3Vlci4uLg ``` 4. Present the transformed URI to users as a [QR code](#displayed-as-a-qr-code-cross-device) or [deep link](#displayed-as-a-hyperlink-or-button-same-device). **Step 3: Verify setup** Test that the domain verification is working: **iOS:** * Install your app on a physical device (Universal Links don't work in the simulator). * Open the HTTPS link in Safari. * The app should open automatically without showing a browser page. **Android:** * Install your app on a device. * Run: `adb shell pm get-app-links com.example.wallet` * Verify the domain shows as "verified". **Trade-offs:** * ✅ Guarantees only your specific app can handle offers from your domain. * ✅ Smoothest user experience with no app selection prompts. * ✅ Highest level of security and control. * ✅ Professional appearance with branded domain links. * ⚠️ Requires domain ownership and web server configuration. * ⚠️ More complex setup than custom schemes. * ⚠️ Requires coordination between issuing and wallet teams. **Requirements for wallet applications:** The wallet app must: * Be configured with Associated Domains (iOS) or intent filters with `autoVerify="true"` (Android). * Handle HTTPS URLs from your domain. * Decode the base64 URL-encoded offer and extract the original OID4VCI offer URI. * Process the offer using the standard OID4VCI flow. See the [holder guide](/docs/holding/credential-claiming-guides/handling-uri-schemes#claimed-https-scheme-app-links--universal-links) for implementation details. ### Security considerations [#security-considerations] Understanding the security implications of each URI scheme is important for choosing the right approach: **URI scheme limitations:** Using a specific URI scheme (custom or private-use) does not prevent other applications from claiming the offer entirely. A technically sophisticated user or malicious app could still extract the offer URI from a QR code or deep link and claim it with a different wallet application. URI schemes are primarily a **user experience mechanism** to: * Make it easier to scan a QR code or follow a deep link and have the OS open the intended app. * Reduce confusion by limiting which apps are presented as options. * For HTTPS schemes, provide verified domain ownership to ensure your app is the default handler. **Enforcing app-level security:** Currently the only way to fully restrict which app can claim a credential offer is through the **OID4VCI Authorization Code flow** with restricted redirect URIs. This involves: 1. Configuring allowed redirect URIs on the MATTR VII side when setting up your tenant. 2. Specifying a redirect URI that only your app can handle (typically using a claimed HTTPS scheme). 3. During authentication, the user is redirected back to this URI, which only your app can intercept. This ensures that only your app can complete the issuance process, even if another app intercepts the initial offer. [Wallet Attestation](/docs/issuance/credential-issuance/wallet-attestation) is a tech-preview feature that enables credential issuers to restrict credential issuance to trusted wallet applications only. It requires the wallet application to present a valid wallet attestation token during the issuance process. The issuer can verify this token to ensure the request is coming from a trusted source. This provides a much stronger security guarantee than relying on URI schemes alone, as it prevents unauthorized applications from claiming credentials even if they can intercept the offer URI. Please [contact us](mailto:dev-support@mattr.global) if you need help configuring restricted redirect URIs for your tenant. ### Presenting credential offers to users [#presenting-credential-offers-to-users] After generating a credential offer (regardless of which URI scheme you've chosen), there are two common ways to present the offer to users. Consider offering both options to accommodate different user contexts and preferences. The URI scheme you chose determines **how** the offer is handled once the user interacts with it. The presentation method determines **where** and **how** the user first encounters the offer. #### Displayed as a hyperlink or button (same-device) [#displayed-as-a-hyperlink-or-button-same-device] For situations where you expect the offer to be presented on the user's mobile device (where they would likely have the holder app installed), you can treat the offer as a hyperlink or button that will invoke your app to handle the offer. **Best practices:** * Make the button or link visually prominent and clearly labeled (e.g., "Add to Wallet", "Claim Credential"). * Consider providing a fallback QR code option for users who encounter issues with the direct link. #### Displayed as a QR code (cross-device) [#displayed-as-a-qr-code-cross-device] For situations where you expect the offer to be presented on a screen other than the user's mobile device (such as a desktop computer or even a printed leaflet), you can display a QR code that can be scanned by the user's device to pick up the offer. **Best practices:** * Ensure the QR code is large enough to be easily scanned (200px square is generally sufficient). * Consider providing instructions or visual cues to guide users on how to scan the code with their wallet app. * For users already on a mobile device, consider also displaying a button or link as an alternative to scanning. # How to create an Apple digital pass template URL: /docs/issuance/cwt-credential-templates/apple-templates An Apple digital pass template is required to [format a CWT or Semantic CWT credential as an Apple digital pass](/docs/issuance/cwt-direct-issuance#format-the-signed-cwt-credential). ## Overview [#overview] Creating an Apple digital pass template comprises the following steps: 1. [Design a template](#design-a-template) 2. [Create a MATTR VII template](#create-a-mattr-vii-template) ## Prerequisites [#prerequisites] * Credentials associated with your organization's [Apple developer account](https://developer.apple.com/programs/). **Sample templates**: You can download the following sample templates to better understand how to structure your own templates as you follow along this guide: * CWT credential [sample Apple digital pass template](https://github.com/mattrglobal/sample-apps/tree/master/credential-templates/compact/apple-pass/WorkingAtHeightsCertVC). * Semantic CWT credential [sample Apple digital pass template](https://github.com/mattrglobal/sample-apps/tree/master/credential-templates/compact-semantic/apple-pass/WorkingAtHeightsCertVC). ### Design a template [#design-a-template] Bundle the following digital pass assets into a `.zip` file: * `pass.json` : This *required* file contains pass information and identifiers. * `footer.png` : Displayed on the front of the pass near the barcode. Only available on boarding passes. * `icon.png` : Displayed in notifications and in emails that have a pass attached, and on the lock screen. When it is displayed, the icon gets a shine effect and rounded corners. * `thumbnail.png` : Displayed on the front of the pass. Only available on generic passes. For example, on a membership card, the thumbnail could be used for a cardholder picture. * `logo.png` : Displayed on the top-left corner of the front of the pass. * `strip.png` : Displayed behind the primary fields on the front of the pass. Only available on store cards, event tickets and coupons. The template `.zip` file cannot be larger than 1mb. Different sized images (e.g. @2x, @3x) can be included in the bundle. Read more about the visual layout of Apple digital passes and [suggested design guidelines](https://developer.apple.com/design/human-interface-guidelines/wallet#Designing-passes). ##### pass.json [#passjson] The contents of `pass.json` are described in detail in [Apple's developer documentation](https://developer.apple.com/documentation/walletpasses/pass). The following is an example `pass.json` file: ```json title="Example pass.json file" { "formatVersion": 1, "organizationName": "Advanced Safety Training", "description": "HS.278 Working at Heights Certification", "labelColor": "rgb(45, 45, 45)", "foregroundColor": "rgb(45, 45, 45)", "backgroundColor": "rgb(202, 202, 202)", "sharingProhibited": true, "voided": false, "barcode": { "format": "PKBarcodeFormatQR", "messageEncoding": "iso-8859-1", "message": "{{encoded}}", "altText": "Exp: {{ date decoded.expiry 'dd MMM yyyy' }}" }, "storeCard": { "headerFields": [ { "key": "codeHeader", "label": "Certification", "value": "{{ decoded.code }}" } ], "primaryFields": [], "secondaryFields": [ { "key": "nameSecondary", "label": "Name", "value": "{{ decoded.name }}" }, { "key": "certificationLevelSecondary", "label": "Certified for", "value": "{{ decoded.certificationLevel }}" } ], "auxiliaryFields": [], "backFields": [ { "key": "nameBack", "label": "Name", "value": "{{ decoded.name }}" }, { "key": "certificationNameBack", "label": "Certification", "value": "{{ decoded.certificationName }}" } ] } } ``` * `formatVersion` (*required*): File format version. The value must be 1. * `organizationName` (*required*): Name of the organization that created and signed the pass. * `description` (*required*): Pass description, used by iOS accessibility technologies. * `labelColor` (*optional*): Label text color, specified as a CSS-style RGB triple. When omitted, label color is automatically determined. * `foregroundColor` (*optional*): Foreground pass color, specified as a CSS-style RGB triple. When omitted, label color is automatically determined. * `sharingProhibited` : Set to `false` to disable the Share Pass option * `voided` : Indicates that the pass is void. For example, a one time use coupon that has been redeemed. The default value is `false`. * `barcode` : * `format` : Must be set to `PKBarcodeFormatQR`. * `messageEncoding` : Must be set to `iso-8859-1`. * `storeCard` : This is the style key which corresponds with the pass's style. Can be either `storeCard` (as shown in the example above), `generic`, `eventTicket`, `coupon` or a `boardingPass`. Each style key has different components. Below is an example of a `storeCard` pass structure: * `headerFields` : Fields to be displayed in the header on the front of the pass. Use header fields sparingly; unlike all other fields, they remain visible when a stack of passes is displayed. * `primaryFields` : Fields to be displayed prominently on the front of the pass. * `secondaryFields` : Fields to be displayed on the front of the pass. * `auxiliaryFields` : Additional fields to be displayed on the front of the pass. * `backFields` : Fields to be on the back of the pass. * `transitType` : this is required for boarding passes and not allowed otherwise. Must be one of the following: `PKTransitTypeAir`, `PKTransitTypeBoat`, `PKTransitTypeBus`, `PKTransitTypeGeneric` or `PKTransitTypeTrain`. * Each field must contain a set of standard field dictionary keys: * `key` : the key must be unique within the scope of the entire pass. * `label` : label text for the field. * `value` : the value of the field. ### Create a MATTR VII template [#create-a-mattr-vii-template] Make a `multipart/form-data` request with the template `.zip` file included as a binary file to [create a CWT credential Apple digital pass template](/docs/issuance/direct-issuance-api-reference/cwt-apple-pass-templates#create-a-cwt-credential-apple-pass-template): ```http title="Request" POST /v2/credentials/compact/digital-pass/apple/templates ``` You can make a similar request to a different endpoint to [create a Semantic CWT credential Apple digital pass template](/docs/issuance/direct-issuance-api-reference/semantic-cwt-apple-pass-templates#create-a-semantic-cwt-credential-apple-pass-template): ```http title="Request" POST /v2/credentials/compact-semantic/digital-pass/apple/templates ``` *Request keys* | Key | Type | Description | | :------------------- | :--: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `template` | File | Attach your `template.zip` file. | | `name` | Text | Insert a name to identify this Apple digital pass template. | | `fileName` | Text | Insert the file name that will be assigned to Apple digital passes created from this template. | | `teamIdentifier` | Text | The [Team ID](https://developer.apple.com/help/account/manage-your-team/locate-your-team-id/) for the Apple Developer Program account that registered the pass type identifier. | | `passTypeIdentifier` | Text | The pass type identifier that’s [registered](https://developer.apple.com/documentation/walletpasses/building_a_pass) with Apple. The value must be the same as the distribution certificate used to sign the pass. | | `wwdr` | Text | Apple G1 or G4 worldwide developer relations intermediate certificate. | | `signerCert` | Text | Apple pass signer [certificate](https://help.apple.com/developer-account/#/devbfa00fef7). | | `signerKey` | Text | The encrypted key of the Apple pass signer certificate. | | `signerPassphrase` | Text | Passphrase for the encrypted key. | *Response* ```json title="Response body" { "id": "1b04f0ee-8e3e-4153-a0e0-8603a10e7f0a", // [!code focus] "name": "example", "passType": "apple", "metadata": { "fileName": "example.pkpass", "teamIdentifier": "xxxxx", "passTypeIdentifier": "xxxxx" } } ``` * `id` : This is a unique identifier for this Apple digital pass template. You will use it as the `templateId` when [formatting a CWT credential as an Apple digital pass](/docs/issuance/cwt-direct-issuance#format-the-signed-cwt-credential). * All other fields in the response include information retrieved from your Apple digital pass template. ### Troubleshooting [#troubleshooting] Errors may occur in the following scenarios: * Unable to decompress the template `.zip` file. * `pass.json` is missing from the bundle or it is malformed. * Required fields in `pass.json` are not available. * The file name contains a special character that is not supported by the Apple pass template endpoint. * The bundle file exceeds the maximum size allowed (1mb). If you ever need to update any issuer information such as `teamIdentifier`, `passTypeIdentifier`, `wwdr`, `signerCert`, `signerKey` or `signerKeyPassphrase`, it is highly recommended to create a new template. However, you can also [update an existing template](/docs/issuance/direct-issuance-api-reference/cwt-apple-pass-templates#update-a-cwt-credential-apple-pass-template). # How to create a Google digital pass template URL: /docs/issuance/cwt-credential-templates/google-templates A Google digital pass template is required to [format a CWT or Semantic CWT credential as a Google digital pass](/docs/issuance/cwt-direct-issuance#format-the-signed-cwt-credential). ## Overview [#overview] Creating a Google digital pass template comprises the following steps: 1. [Design a template](#design-a-template) 2. [Create a MATTR VII template](#create-a-mattr-vii-template) ## Prerequisites [#prerequisites] * Credentials associated with your organization's [Google account](https://cloud.google.com/iam/docs/service-accounts). **Sample templates**: You can download the following sample templates to better understand how to structure your own templates as you follow along this guide: * CWT credential [sample Google digital pass template](https://github.com/mattrglobal/sample-apps/tree/master/credential-templates/compact/google-pass/WorkingAtHeightsCertVC). * Semantic CWT credential [sample Google digital pass template](https://github.com/mattrglobal/sample-apps/tree/master/credential-templates/compact-semantic/google-pass/WorkingAtHeightsCertVC). Note that our provided template offers greater flexibility in designing the digital pass, while the suggested template in the code snippets below would be easier to get started with. Either way. it is highly recommended to use Google's [Generic Pass template](https://developers.google.com/wallet/generic/resources/template). ### Design a template [#design-a-template] Create your template in a JSON file and then bundle it into a `.zip` file. The JSON file contains the digital pass information, identifiers and value mappings. It is structured as follows: The template `.zip` file cannot be larger than 1mb. ```json title="Example template.json file" { "genericClass": { "textModulesData": [ { "header": "Name", "body": "{{ decoded.name }}" }, { "header": "Certification", "body": "{{ decoded.certificationName }}" }, { "header": "Code", "body": "{{ decoded.code }}" }, { "header": "Certified for", "body": "{{ decoded.certificationLevel }}" }, { "header": "Expires on", "body": "{{ date decoded.expiry 'dd MMM yyyy' }}" }, { "header": "Issued by", "body": "{{ decoded.issuerName }}" } ] }, "genericObject": { "genericType": "GENERIC_TYPE_UNSPECIFIED", "cardTitle": { "defaultValue": { "language": "en-US", "value": "Working at Heights Certification" } }, "header": { "defaultValue": { "language": "en-US", "value": "{{ decoded.certificationName }}" } }, "subHeader": { "defaultValue": { "language": "en-US", "value": "Advanced Safety Training" } }, "logo": { "sourceUri": { "uri": "https://YOUR_WEB_DOMAIN/LINK_TO_LOGO_IMAGE.png", "description": "" } }, "hexBackgroundColor": "#CACACA", "heroImage": { "sourceUri": { "uri": "https://YOUR_WEB_DOMAIN/LINK_TO_HERO_IMAGE.png", "description": "" } }, "barcode": { "type": "QR_CODE", "value": "{{encoded}}", "alternateText": "QR code" } } } ``` * `genericClass` : Contains common data across objects. Text fields on the pass face are defined here. Each text module contains a header and a body for each row. To add text to the pass details, set the values in the `textModulesData`. Refer to [Google Wallet GenericClass documentation](https://developers.google.com/wallet/reference/rest/v1/genericclass) for more options. * `genericObject` : Represents each Generic Pass a user has in their Google Wallet app. It contains a number of configurable attributes such as card title, header, sub header, logo, background color, hero image and barcode. The image assets should be uploaded and publicly accessible. Refer to [Google Wallet GenericObject documentation](https://developers.google.com/wallet/reference/rest/v1/genericobject) for more options. ### Create a MATTR VII template [#create-a-mattr-vii-template] Make a `multipart/form-data` request with the template `.zip` file included as a binary file to [create a CWT credential Google digital pass template](/docs/issuance/direct-issuance-api-reference/cwt-google-pass-templates#create-a-cwt-credential-google-pass-template): ```http title="Request" POST /v2/credentials/compact/digital-pass/google/templates ``` You can make a similar request to a different endpoint to [create a Semantic CWT credential Google digital pass template](/docs/issuance/direct-issuance-api-reference/semantic-cwt-google-pass-templates#create-a-semantic-cwt-credential-google-pass-template): ```http title="Request" POST /v2/credentials/compact-semantic/digital-pass/google/templates ``` *Request keys* | Key | Type | Description | | :-------------------------- | :--: | :-------------------------------------------------------------------------------------------------- | | `template` | File | Include your template `.zip` file. | | `name` | Text | Insert a name to identify this Google digital pass template. | | `issuerId` | Text | Google Wallet Pass signer issuer ID. | | `serviceAccountClientEmail` | Text | Email address of the Google Cloud Platform service account for accessing the Google Pay Passes API. | | `serviceAccountPrivateKey` | Text | Private key PEM of the Google Cloud Platform service account. | Learn how to grant access to your service account to call the [Google Wallet API](https://developers.google.com/wallet/generic/getting-started/onboarding-guide#3_create_a_service_account). *Response* ```json title="Response body" { "id": "0793fade-bd27-46a8-8dfe-67c4d3e9cf09", // [!code focus] "name": "example", "passType": "google", "metadata": { "issuerId": "xxxxx", "serviceAccountClientEmail": "xxxxx", "payPassId": "xxxxx" } } ``` * `id` : This is a unique identifier for this Apple digital pass template. You will use it as the `templateId` when [formatting a CWT credential as a Google digital pass](/docs/issuance/cwt-direct-issuance#format-the-signed-cwt-credential). * All other fields in the response include information retrieved from your Google digital pass template. ### Troubleshooting [#troubleshooting] Errors may occur in the following scenarios: * Unable to decompress the template `.zip` file. * `template.json` is missing from the bundle or it is malformed. * The template `.zip` file exceeds the maximum size allowed (1mb). To avoid potential impact on previously generated Google digital passes, [updating an existing template](/docs/issuance/direct-issuance-api-reference/cwt-google-pass-templates#update-a-cwt-credential-google-pass-template) will always create a new template in the Google Pay Business Console. # How to create a PDF template URL: /docs/issuance/cwt-credential-templates/pdf-templates A PDF template is required to [format a CWT or Semantic CWT credential as a PDF document](/docs/issuance/cwt-direct-issuance#format-the-signed-cwt-credential). ## Overview [#overview] Creating a PDF template comprises the following steps: 1. [Design a template](#design-a-template) 2. [Create a MATTR VII template](#create-a-mattr-vii-template) **Sample templates**: You can download the following sample templates to better understand how to structure your own templates as you follow along this guide: * CWT credential [sample PDF template](https://github.com/mattrglobal/sample-apps/tree/master/credential-templates/compact/pdf/WorkingAtHeightsCertVC). * Semantic CWT credential [sample PDF template](https://github.com/mattrglobal/sample-apps/tree/master/credential-templates/compact-semantic/pdf/WorkingAtHeightsCertVC). ### Design a template [#design-a-template] Bundle the following template components into a `.zip` file: * [`template.pdf`](#templatepdf): This is a required PDF file used as the PDF template. * [`config.json`](#configjson): This is a required JSON file used for field mappings. * `fonts` : This is an optional folder that includes any custom fonts being used in the template in `.otf` or `.ttf` format. The template `.zip` file cannot be larger than 700kb. ##### template.pdf [#templatepdf] * Maximum size of `template.pdf` is **700kb**. * The template must have a `qrCode` button field. The QR code is generated from the payload and there is no value mapping required for this field. * All other fields in the template should be plain text fields * Other field types (e.g. checkboxes, radio buttons, list boxes, dropdown boxes) are not supported. * No macros, rules, formulation or calculations are supported. * It is possible to have identical claims in the same PDF. The field names be suffixed (e.g. `firstName#1`, `firstName#2`). * Multi-page templates are supported as long as the `qrCode` field is on the first page. * Reading order for each field should be specified to support accessibility. Unselect `Display like elements in a single block`. ##### config.json [#configjson] This JSON file should be structured as follows: ```json title="Example config.json file" { "name": "SamplePDF_WorkingAtHights", "fileName": "{{ vc.credentialSubject.code }}_{{ vc.credentialSubject.name }}", "metadata": { "title": "{{ vc.credentialSubject.certificationName }} Certification – {{ vc.credentialSubject.name }}" }, "fonts": [ { "name": "PublicSans-Regular", "fileName": "PublicSans-Regular.ttf" }, { "name": "PublicSans-Bold", "fileName": "PublicSans-Bold.ttf" } ], "fields": [ { "key": "name", "value": "{{ vc.credentialSubject.name }}", "isRequired": true, "alternativeText": "{{ vc.credentialSubject.name }}", "fontName": "PublicSans-Regular" }, { "key": "code", "value": "{{ vc.credentialSubject.code }}", "isRequired": true, "alternativeText": "{{ vc.credentialSubject.code }}", "fontName": "PublicSans-Bold" } ] } ``` * `name` : Template name. * `fileName` : Use values from your `template.pdf` to set the generated PDF file name. * `metadata` : Use values from your `template.pdf` to set the generated PDF metadata. * `fonts` : This array includes custom fonts that are used in your PDF template. Note that these custom fonts must be included in a `fonts` folder in your `template.zip` bundle for the template to be valid. * `name` : The name of the font to be referenced by fields using it. * `fileName` : The name of the font `.otf`/`.ttf` file in the fonts folder. * `fields` : This array includes fields that are defined in your `template.pdf` file: - `key` : Field name in the `template.pdf` file. - `value` : Mapped claim from the credential payload. - `isRequired` : When set to `true`, the value must be provided in the credential payload to generate a valid PDF. If it is not provided, an error would occur. - `alternativeText` : Alternative text to support accessibility. Must be specified for every field except for `qrCode`. If the value is defined in both `template.pdf` and `config.json`, the value from `config.json` is used. - `fontName` : Custom font name to display the field. When no font is specified *Helvetica* is used by default. Note that this default font only supports Windows-1252 encoding character sets. Refer to [Internationalization implementation considerations](/docs/concepts/cwt/implementation#internationalization---how-can-i-support-or-display-multiple-languages) for more information. ### Create a MATTR VII template [#create-a-mattr-vii-template] Make a request of the following structure with the template `.zip` file included as a binary file to [create a CWT credential PDF template](/docs/issuance/direct-issuance-api-reference/cwt-pdf-templates#create-a-cwt-credential-pdf-template): ```http title="Request" POST /v2/credentials/compact/pdf/templates Header: Content-Type: application/zip ``` You can make a similar request to a different endpoint to [create a Semantic CWT credential PDF template](/docs/issuance/direct-issuance-api-reference/semantic-cwt-pdf-templates#create-a-semantic-cwt-credential-pdf-template): ```http title="Request" POST /v2/credentials/compact-semantic/pdf/templates Header: Content-Type: application/zip ``` *Response* ```json title="Request body" { "id": "682f203a-f5bd-4304-95e3-c8c708e90d26", // [!code focus] "name": "SamplePDF_WorkingAtHights", "fileName": "{{ vc.credentialSubject.code }}_{{ vc.credentialSubject.name }}", "fonts": [ { "name": "PublicSans-Regular", "fileName": "PublicSans-Regular.ttf" }, { "name": "PublicSans-Bold", "fileName": "PublicSans-Bold.ttf" } ], "fields": [ { "key": "name", "value": "{{ vc.credentialSubject.name }}", "isRequired": true, "alternativeText": "{{ vc.credentialSubject.name }}", "fontName": "PublicSans-Regular" }, { "key": "code", "value": "{{ vc.credentialSubject.code }}", "isRequired": true, "alternativeText": "{{ vc.credentialSubject.code }}", "fontName": "PublicSans-Bold" }, { "key": "certificationName", "value": "{{ vc.credentialSubject.certificationName }}", "isRequired": true, "alternativeText": "{{ vc.credentialSubject.certificationName }}", "fontName": "PublicSans-Bold" }, { "key": "certificationLevel", "value": "{{ vc.credentialSubject.certificationLevel }}", "isRequired": true, "alternativeText": "{{ vc.credentialSubject.certificationLevel }}", "fontName": "PublicSans-Regular" }, { "key": "expiry", "value": "{{ date vc.credentialSubject.expiry 'dd MMM yyyy' }}", "isRequired": true, "alternativeText": "{{ date vc.credentialSubject.expiry 'PPP' }}", "fontName": "PublicSans-Regular" } ], "metadata": { "title": "{{ vc.credentialSubject.certificationName }} Certification – {{ vc.credentialSubject.name }}" } } ``` * `id` : This is a unique identifier for this PDF template. You will use it as the `templateId` when [formatting a CWT credential as a PDF](/docs/issuance/cwt-direct-issuance#format-the-signed-cwt-credential). * All other fields in the response include information retrieved from your PDF template. ### Troubleshooting [#troubleshooting] Errors may occur in the following scenarios: * Unable to decompress the template `.zip` file. * Either `template.pdf` or `config.json` are missing from the bundle. * Either `template.pdf` or `config.json` are malformed. * Custom fonts are used but not provided in `template.zip`. * Either the template `.zip` file or `template.pdf` exceed the maximum allowed file size (700kb). * Fields set as required in `config.json` are not available in `template.pdf`. # Apple Pass templates URL: /docs/issuance/direct-issuance-api-reference/cwt-apple-pass-templates ## Create a CWT credential Apple Pass template [#create-a-cwt-credential-apple-pass-template] ## Retrieve all CWT credential Apple Pass templates [#retrieve-all-cwt-credential-apple-pass-templates] ## Retrieve a CWT credential Apple Pass template [#retrieve-a-cwt-credential-apple-pass-template] ## Update a CWT credential Apple Pass template [#update-a-cwt-credential-apple-pass-template] ## Delete a CWT credential Apple Pass template [#delete-a-cwt-credential-apple-pass-template] # Google Pass templates URL: /docs/issuance/direct-issuance-api-reference/cwt-google-pass-templates ## Create a CWT credential Google Pass template [#create-a-cwt-credential-google-pass-template] ## Retrieve all CWT credential Google Pass templates [#retrieve-all-cwt-credential-google-pass-templates] ## Retrieve a CWT credential Google Pass template [#retrieve-a-cwt-credential-google-pass-template] ## Update a CWT credential Google Pass template [#update-a-cwt-credential-google-pass-template] ## Delete a CWT credential Google Pass template [#delete-a-cwt-credential-google-pass-template] # Issuance URL: /docs/issuance/direct-issuance-api-reference/cwt-issuance ## Sign a CWT credential [#sign-a-cwt-credential] ## Format a CWT credential as a QR code [#format-a-cwt-credential-as-a-qr-code] ## Format a CWT credential as a PDF [#format-a-cwt-credential-as-a-pdf] ## Format a CWT credential as an Apple Pass [#format-a-cwt-credential-as-an-apple-pass] ## Format a CWT credential as a Google Pass [#format-a-cwt-credential-as-a-google-pass] # PDF templates URL: /docs/issuance/direct-issuance-api-reference/cwt-pdf-templates ## Create a CWT credential PDF template [#create-a-cwt-credential-pdf-template] ## Retrieve all CWT credential PDF templates [#retrieve-all-cwt-credential-pdf-templates] ## Retrieve a CWT credential PDF template [#retrieve-a-cwt-credential-pdf-template] ## Update a CWT credential PDF template [#update-a-cwt-credential-pdf-template] ## Delete a CWT credential PDF template [#delete-a-cwt-credential-pdf-template] # Apple Pass templates URL: /docs/issuance/direct-issuance-api-reference/semantic-cwt-apple-pass-templates ## Create a Semantic CWT credential Apple Pass template [#create-a-semantic-cwt-credential-apple-pass-template] ## Retrieve all Semantic CWT credential Apple Pass templates [#retrieve-all-semantic-cwt-credential-apple-pass-templates] ## Retrieve a Semantic CWT credential Apple Pass template [#retrieve-a-semantic-cwt-credential-apple-pass-template] ## Update a Semantic CWT credential Apple Pass template [#update-a-semantic-cwt-credential-apple-pass-template] ## Delete a Semantic CWT credential Apple Pass template [#delete-a-semantic-cwt-credential-apple-pass-template] # Google Pass templates URL: /docs/issuance/direct-issuance-api-reference/semantic-cwt-google-pass-templates ## Create a Semantic CWT credential Google Pass template [#create-a-semantic-cwt-credential-google-pass-template] ## Retrieve all Semantic CWT credential Google Pass templates [#retrieve-all-semantic-cwt-credential-google-pass-templates] ## Retrieve a Semantic CWT credential Google Pass template [#retrieve-a-semantic-cwt-credential-google-pass-template] ## Update a Semantic CWT credential Google Pass template [#update-a-semantic-cwt-credential-google-pass-template] ## Delete a Semantic CWT credential Google Pass template [#delete-a-semantic-cwt-credential-google-pass-template] # Issuance URL: /docs/issuance/direct-issuance-api-reference/semantic-cwt-issuance ## Sign a Semantic CWT credential [#sign-a-semantic-cwt-credential] ## Format a Semantic CWT credential as a QR code [#format-a-semantic-cwt-credential-as-a-qr-code] ## Format a Semantic CWT credential as a PDF [#format-a-semantic-cwt-credential-as-a-pdf] ## Format a Semantic CWT credential as an Apple Pass [#format-a-semantic-cwt-credential-as-an-apple-pass] ## Format a Semantic CWT credential as a Google Pass [#format-a-semantic-cwt-credential-as-a-google-pass] # PDF templates URL: /docs/issuance/direct-issuance-api-reference/semantic-cwt-pdf-templates ## Create a Semantic CWT credential PDF template [#create-a-semantic-cwt-credential-pdf-template] ## Retrieve all Semantic CWT credential PDF templates [#retrieve-all-semantic-cwt-credential-pdf-templates] ## Retrieve a Semantic CWT credential PDF template [#retrieve-a-semantic-cwt-credential-pdf-template] ## Update a Semantic CWT credential PDF template [#update-a-semantic-cwt-credential-pdf-template] ## Delete a Semantic CWT credential PDF template [#delete-a-semantic-cwt-credential-pdf-template] # API Reference URL: /docs/issuance/pre-authorized-code/api-reference ## Create Credential Offer [#create-credential-offer] ## Delete Credential Offer [#delete-credential-offer] ## Issue a verifiable credential [#issue-a-verifiable-credential] ## Retrieve issuer metadata [#retrieve-issuer-metadata] # OID4VCI Pre-authorized Code flow journey pattern URL: /docs/issuance/pre-authorized-code/journey-pattern This journey pattern is used to issue credentials of different formats to a holder via the OID4VCI [Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview) protocol. ## Overview [#overview] * **Issuance channel**: Remote, Unsupervised * **Device/s**: On-device / Cross-device / in-person * **Formats**: mDocs * **Information assurance level**: High * **Identity assurance level**: High (depends on the mechanism used by the issuer to authenticate the holder prior to sharing a credential offer) ## Journey flow [#journey-flow] OID4VCI Pre-authorized Code journey pattern ## Architecture [#architecture] OID4VCI architecture ### Logging into a provider's portal [#logging-into-a-providers-portal] The underlying architecture assumes that the Issuer can reliably confirm the user's identity before issuing the credential offer. This enables seamless, low-friction issuance experiences while preserving trust and security. ### Scanning the QR code [#scanning-the-qr-code] The QR code used to initiate the issuance workflow is generated by the Issuer, while the Holder controls when to scan it using their digital wallet. Scanning the QR code triggers the credential issuance process. ### Credential offer [#credential-offer] Once the QR code is scanned it will result in the wallet displaying the credential offer that was created by the Issuer using MATTR VII issuance capabilities. When the QR code is scanned, the Holder’s digital wallet initiates the credential issuance workflow and displays the credential offer prepared by the Issuer using MATTR VII's issuance capabilities. The offer outlines the credential formats to be issued and specifies the claims included in each credential. In a pre-authorized flow, the Issuer can gather information about the intended Holder in advance—since the offer is created for a known user—allowing the Issuer to tailor the credential with specific, user-relevant claims. Digital trust service capabilities enable creating and maintaining policies that define what Issuers can be trusted and what credential types they are allowed to issue. This introduces an additional level of trust to interactions within the trust network, making it easier for Samantha to decide whether or not she wishes to claim a credential from this Issuer. ### Obtaining a binding attribute [#obtaining-a-binding-attribute] The OpenID Credential Provisioning component commences the credential issuance flow by obtaining a unique binding attribute from the requesting device/wallet. This happens when the user accepts the credential offer. The binding attribute is carried through the proceeding steps to bind the intended credential holder and the data. ### Transaction code [#transaction-code] To enhance the security of the pre-authorized issuance flow, the Issuer may optionally require the Holder to provide a transaction code before the credential can be claimed. This code is generated by the Issuer as part of the credential offer and is uniquely associated with that offer. The Issuer sends the transaction code to the user through a secure, alternative communication channel such as email or SMS. When the user initiates the issuance process by scanning the QR code, they are prompted by the wallet to enter the transaction code. The credential can only be issued if the correct code is supplied. This optional verification step strengthens the assurance that the credential is issued to the intended recipient, helping to prevent unauthorized access in scenarios where the credential offer may have been intercepted or misdirected. ### Credential issuance [#credential-issuance] The information then gets passed back through the OpenID Credential Provisioning component to map against an established vocabulary, and express the intended context around each piece of information it holds. The mapped data is then passed to the Credential Generation component which formats, binds and signs the data into a credential that is ready to be sent to the requesting wallet/device. ### Credential management [#credential-management] Digital wallets can be used to manage the acceptance and secure storage of the credential on the Holder’s device upon completion of the credential issuance flow. This can be achieved by wallets built with our MATTR Pi Wallet SDK or branded MATTR GO Hold applications. # OID4VCI Pre-authorized Code flow URL: /docs/issuance/pre-authorized-code/overview ## Overview [#overview] The Pre-authorized Code flow is a streamlined, user-friendly process where the credential recipient (usually a wallet) is issued a pre-authorized code by the issuer (e.g., a government or organization). This code can be used to obtain a credential without requiring the user to go through the full authentication process again. In the pre-authorized code flow, the issuer provides a pre-authorized code directly to the wallet. The wallet uses this code to obtain an access token, which it then uses to request the issuance of a credential. This flow is ideal for scenarios where the user has already been authenticated and authorized, and the issuer wants to simplify the credential issuance process. It is particularly useful for situations where the user needs to obtain multiple credentials or where the issuance process needs to be expedited. The Pre-authorized Code flow is only supported for [mDocs](/docs/concepts/mdocs). ## Workflow [#workflow] The following diagram depicts the OID4VCI Pre-authorized Code flow: ### Issuer preparation [#issuer-preparation] The issuer prepares the credential issuance by authenticating the user and gathering claims about them. This step is performed outside of the OID4VCI workflow and is not performed using MATTR capabilities. The issuer can use any method to authenticate the user, such as a login page or a custom implementation. The issuer then passes the user claims to MATTR VII, which will be used later in the credential issuance process. ### Creating a Credential offer [#creating-a-credential-offer] Once the issuer is ready, they initiate the creation of a [credential offer](/docs/issuance/credential-offer/overview) by making a request that specifies: * The [credential configuration](/docs/issuance/credential-configuration/overview) to use for issuance. * The user claims to be included in the credential. * The credential’s expiry date. * Whether a transaction code is required for claiming the credential. MATTR VII will then create a credential offer that includes a pre-authorized code, which is used to issue the credential. This code is a unique identifier that allows the wallet to claim the credential without requiring the user to go through the full authentication process again. The MATTR VII response will include a URI that can be used by the wallet to accept the credential offer and request the credential. ### Sharing a Credential offer [#sharing-a-credential-offer] The issuer can now share the credential offer with the intended holder. This is done by sending the credential offer URI to the wallet. The URI can be shared in various ways, such as a QR code, a deep link, or a push notification. The issuer can also choose to share the transaction code separately if it is required for claiming the credential. Refer to [Claiming credential offers](/docs/issuance/credential-offer/overview#claiming-credential-offers) for more details on how to adjust the offer URI to use specific schemes and the resulting user experience. ### Accepting a Credential offer [#accepting-a-credential-offer] After receiving the credential offer URI, the wallet uses it to retrieve required metadata from the `.well-known` endpoints: ```http title="Issuer Metadata Endpoint" https://{your_tenant_url}/.well-known/openid-credential-issuer ``` ```http title="Authorization Server Metadata Endpoint" https://{your_tenant_url}/.well-known/oauth-authorization-server ``` As required by the OID4VCI specification, these endpoints must be publicly accessible. They provide the information necessary to initiate the issuance process, including the issuer’s details, the types of credentials available, and the relevant endpoints and supported parameters used throughout the workflow. If the credential offer includes a transaction code requirement, the wallet prompts the user to enter the code before proceeding. The transaction code is validated by the issuer when the wallet calls the token endpoint as part of the token exchange. If an incorrect transaction code is entered three times, the credential offer is permanently invalidated and the wallet can no longer claim the credential. The wallet then calls the issuer’s token endpoint, exchanging the pre-authorized code (and, where required, the transaction code) for an access token. This token endpoint is hosted by MATTR VII and is accessible only when a valid pre-authorized code is provided. **Single-use offers**: Pre-authorized Code flow offers are designed for a specific user who has already been authenticated out-of-band. Once the offer is successfully claimed and the credential is issued, the pre-authorized code is consumed and cannot be reused. Any subsequent attempts to claim the same offer will fail, even by the same user. This is a security measure to prevent unauthorized credential duplication. If you need to issue another credential to the same user, you must generate a new credential offer. Finally, the wallet uses the returned access token to call the issuer’s credential issuance endpoint and request the credential. This endpoint, also hosted by MATTR VII, is accessible only when a valid access token is included in the request. ### Formatting and Signing the credential [#formatting-and-signing-the-credential] When the wallet requests the credential, MATTR VII takes the data provided by the issuer and formats it into a digital credential. This credential is then cryptographically signed. The process uses a [credential configuration](/docs/issuance/credential-configuration/overview) specified in the credential offer, which acts as a template for the issued credential. The credential configuration defines how claims are mapped, the credential’s appearance in the wallet, its expiration, and whether it can be revoked. ### Delivering the signed credential [#delivering-the-signed-credential] Finally, the signed credential is delivered directly to the holder’s digital wallet and can be presented to verifying parties upon request. ## Configuration [#configuration] The OID4VCI workflow makes use of different components that can be configured either using direct API requests to a MATTR VII tenant or via the [MATTR Portal](/docs/platform-management/portal), which offers an interface layer on top of the APIs. * Issuer identity management (*required*): Set up and manage the identifiers that represent your issuer and are essential for establishing the issuer's identity in the credential issuance process. As the Pre-authorized Code only supports mDocs, this means creating an [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca). * [Credential configuration](/docs/issuance/credential-configuration/overview) (*required*): Add your credential types, branding, claims, and other metadata. You can also mix and match where claims for the issued credentials come from - an authentication provider or a claims source. * [Credential offer](/docs/issuance/credential-offer/overview) (*required*): This is the main component of the OID4VCI Pre-authorized Code flow. It contains the credential configuration identifier, the pre-authorized code, and other metadata. The credential offer is used to initiate the issuance process and includes all necessary information for the wallet to request and claim the credential. * [Claims source](/docs/issuance/claims-source/overview) (*optional*): While the intent of the Pre-authorized Code flow is for the issuer to provide all the required claims when creating the credential offer, there may be cases where additional claims are needed. If you have additional user information stored in a separate database or service, add a claims source to fetch claims directly from a compatible standalone system and use these claims when issuing credentials. # OID4VCI Pre-authorized Code flow quickstart guide URL: /docs/issuance/pre-authorized-code/quickstart This quickstart is for evaluating MATTR’s OID4VCI Pre-authorized Code flow issuance capabilities. In about 10-15 minutes you will configure an OID4VCI Pre-authorized Code flow in the MATTR Portal, generate a credential offer, and claim an mDoc into the GO Hold example app. **Estimated time: 10-15 minutes.** Use this guide as a fast evaluation path to see the flow working end-to-end. For detailed information and API examples, explore the [tutorial](/docs/issuance/pre-authorized-code/tutorial) and reference documentation. ## User experience [#user-experience] In this quickstart you will perform this exact flow yourself using the MATTR Portal and the GO Hold example app: OID4VCI Tutorial Workflow 1. User scans a QR code from an issuer. 2. The wallet displays what credential is being offered. 3. The user accepts the offer and provides a transaction code. 4. The credential is immediately issued to the wallet. In this flow there is no user authentication step, making it a great option for low-risk credentials or use cases where the user has already authenticated through another channel (e.g., a mobile app). ## Prerequisites [#prerequisites] * MATTR VII tenant access via the [MATTR Portal](https://portal.mattr.global/). Apply for access [here](/docs/resources/get-started). * Install the **MATTR GO Hold example app** for [iOS](https://apps.apple.com/app/mattr-wallet/id1518660243) or [Android](https://play.google.com/store/apps/details?id=global.mattr.wallet). ## Steps [#steps] ### Create issuer certificate (2 minutes) [#create-issuer-certificate-2-minutes] This allows your tenant to act as an issuing authority for mDocs in this demo. This step is only required if you haven't already set up an issuer certificate for your tenant. If you already have an active IACA, skip to the next step. 1. Log into the [MATTR Portal](https://portal.mattr.global/). 2. Switch to your tenant if you have access to multiple tenants, or [create a new tenant](/docs/platform-management/portal#creating-a-tenant) if needed. 3. Expand **Platform Management**. 4. Select **Certificates**. 5. Select **Create new**. 6. Select **IACA - Issuing Authority Certificate Authority** as the type. 7. Select **MATTR managed** as the management method. 8. Select **Create**. 9. Set **Status** to **Active**. 10. Select **Update** to activate the certificate. ### Create mDoc credential configuration (3 minutes) [#create-mdoc-credential-configuration-3-minutes] In this quickstart you’ll use a simple credential configuration so you can issue a credential without integrating any external data sources: 1. Expand **Credential Issuance**. 2. Select **mDocs**. 3. Select **Create new**. 4. Enter a **Name** (e.g., "My First Credential"). 5. Enter a **Description** (e.g., "Claimed via Pre-authorized Code flow"). 6. Enter a **Credential type** (e.g., `com.example.preauthcredential`). 7. Paste the following JSON into **Claim mappings**: ```json title="Claim mappings object" { "com.example.personaldetails.1": { "name": { "defaultValue": "Emma Tasma", "type": "string" }, "email": { "defaultValue": "emma.tasma@example.com", "type": "string" } } } ``` 8. Enter "1" in the **Months** field under **Validity for**. 9. Select **Create**. ### Generate a credential offer (2 minutes) [#generate-a-credential-offer-2-minutes] This creates the OID4VCI offer that wallets can use to start the Pre-authorized Code flow. 1. Expand **Credential Issuance**. 2. Select **Credential offer**. 3. Select **Pre-authorized code flow** as the workflow. 4. Select the **Select** button. 5. Check the checkbox next to your credential configuration. 6. Select **Apply**. 7. Select **Generate**. 8. Copy the Transaction code that is displayed on the screen.\ In production deployments you will need to provide it to the user through a different channel (e.g., email, SMS). 9. Download the displayed QR code (or just leave it on the screen for scanning in the next step). ### Claim the credential (2 minutes) [#claim-the-credential-2-minutes] Now use the GO Hold example wallet to experience the end-to-end flow from QR scan to credential in the wallet: 1. Open the [**GO Hold example app**](/docs/holding/go-hold/getting-started). 2. Select **Share** on the home screen. 3. Select **Respond or Collect** (You may need to allow the app to access your camera). 4. Scan the QR code you generated in the previous step. 5. Review the credential offer and select **Proceed**. 6. Enter the Transaction code you copied earlier. 7. The new credential should now be visible in the GO Hold example app. Behind the scenes, MATTR handled the OID4VCI Pre-authorized Code flow, including validating the Transaction code and credential offer details and issuing the mDoc to your GO Hold example app. Congratulations! You've successfully configured an OID4VCI Pre-authorized Code flow and issued an mDoc to a digital wallet using only MATTR Portal configuration and the GO Hold example app. ## Next steps [#next-steps] * For your evaluation: * Note how long this quickstart took and any friction you encountered. * Review the credential configuration and consider how it would map to your real data. * Explore the [OID4VCI Pre-authorized Code flow tutorial](/docs/issuance/pre-authorized-code/tutorial) for detailed instructions and explanations. * Try the [OID4VCI Authorization Code flow](/docs/issuance/authorization-code/quickstart) for a different issuance workflow with user authentication. # Learn how to configure an OID4VCI Pre-authorized Code flow to issue an mDoc into a digital wallet URL: /docs/issuance/pre-authorized-code/tutorial ## Introduction [#introduction] The [OID4VCI specification](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) is an open standard developed by the OpenID Foundation. It leverages the OpenID protocol to support verifiable credentials issuance and management. In this tutorial we will configure an [OID4VCI Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview) and use it to issue an [mDoc](/docs/concepts/mdocs) into a [MATTR GO Hold example app](/docs/holding/go-hold/getting-started), showing each step both through the MATTR Portal and via the equivalent API calls. ## User experience [#user-experience] OID4VCI Tutorial Workflow This is the user experience you will build in this tutorial: 1. The user launches their [GO Hold example app](/docs/holding/go-hold/getting-started) and scans a QR code received from an issuer. 2. The GO Hold example app displays what credential is being offered and by what issuer. 3. Once the user accepts the offer, they are required to provide a transaction code. This code is provided to the user by the issuer through a different channel (e.g. email, SMS, printed on paper). 4. Once the transaction code is provided, the credential is immediately issued to the user's GO Hold example app. They can now view the credential and present it for verification. **UX considerations** * *No prior authentication required*: Users are not required to authenticate before the credential is issued. This simplifies the process but assumes the issuer has already verified the user prior to providing the credential offer (e.g., during login to a wallet application or service portal). * *Implicit credential acceptance*: The user is not technically required to explicitly accept the credential offer. Depending on how the flow is implemented with wallet providers, the credential can be issued immediately upon scanning the QR code. * *Optional transaction code*: The use of a transaction code is optional and can be omitted if not required by the issuer. In this tutorial, we include it to demonstrate how an additional layer of security can be added to the issuance workflow. ## Prerequisites [#prerequisites] * Complete the [sign up form](/docs/resources/get-started) to get trial access to MATTR VII and the MATTR Portal, and then [Create a tenant](/docs/platform-management/portal#creating-a-tenant). * Install the **MATTR GO Hold example app** by following the [getting started guide](/docs/holding/go-hold/getting-started). We recommend using the MATTR VII [Postman collection](/docs/api-reference#postman-collection) in this tutorial. While this isn't an explicit prerequisite it can really speed things up. ## Tutorial overview [#tutorial-overview] To build this user experience, the current tutorial comprises the following steps: 1. [Create Issuer certificates](#create-issuer-certificates): Required to sign mDocs. 2. [Create an mDocs credentials configuration](#create-a-mattr-vii-mdocs-credentials-configuration): Controls the content and branding of issued Credentials. 3. [Create and share a Credential offer](#create-a-credential-offer): Used by digital wallets to trigger the issuance workflow. 4. [Claim the credential as the holder](#use-the-mattr-go-example-app-to-claim-the-credential): Use your GO Hold example app to claim the offered Credential. ## Tutorial steps [#tutorial-steps] ### Create issuer certificates [#create-issuer-certificates] In this tutorial you are going to issue an [mDoc](/docs/concepts/mdocs), so you need to have valid [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca). 1. Log into the [MATTR Portal](https://portal.mattr.global/). 2. In the navigation panel on the left-hand side, expand the **Platform Management** menu. 3. Select **Certificates**. 4. Select the **Create new** button. 5. Use the *Type* radio button to select **IACA - Issuing Authority Certificate Authority**. 6. Use the *Management method* radio button to select **MATTR managed**. 7. Use the *Country* dropdown list to select an issuing country. 8. Select the **Create** button to create the IACA certificate.\ The IACA is created as *inactive* by default. 9. Use the *Status* radio button to select **Active**. 10. Select the **Update** button to activate the IACA certificate. **Step 1: Obtain a MATTR VII access token** All of the MATTR VII endpoints you will use in this tutorial are protected, so you will first need to use your [tenant details](/docs/platform-management/portal#getting-started) to make a request of the following structure and obtain an access token: ```http title="Request" POST https://{auth_server}/oauth/token ``` * `auth_server` : Replace with the `auth_url` value from your tenant details. ```json title="Request body" { "client_id": "F5qae****************************", "client_secret": "Wzc8J**********************************************************", "audience": "learn.vii.au01.mattr.global", "grant_type": "client_credentials" } ``` * Replace `client_id`, `client_secret` and `audience` with your own tenant details. * Keep `grant_type` as `client_credentials`. *Response* ```json title="Response body" { "access_token": "eyJhb********************************************************************", "expires_in": 14400, "token_type": "Bearer" } ``` Use the returned `access_token` value as a bearer token for all requests to your MATTR VII tenant in the next steps of this tutorial. You will need to obtain a new access token whenever it expires. We recommend using our [Postman collection](/docs/api-reference#postman-collection) to interact with your MATTR VII tenant. This collection includes a pre-request script to obtain an access token when it is missing or expired, as well as pre-configured templates for all available requests. **Step 2: Create the IACA certificate** 1. Make the following request to your MATTR VII tenant to create an [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca): ```http filename:"Request" POST /v2/credentials/mobile/iacas ``` The response will include an `id` element which you will use in the next step. 2. Make the following request to activate the IACA you just created: ```http filename:"Request" PUT /v2/credentials/mobile/iacas/{iacaId} ``` * `iacaId`: Replace with the `id` element returned in the response when you created the IACA in the previous steps. ```http filename:"Request body" { "active": true } ``` Your IACA is now active and can be used to sign mDocs. ### Create a MATTR VII mDocs credential configuration [#create-a-mattr-vii-mdocs-credential-configuration] 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **mDocs**. 3. Select the **Create new** button. 4. In the *Name* text box, enter a clear and descriptive title that will appear on the credential in the wallet, for example "My First Pre-Auth Credential". 5. In the *Description* text box, enter a clear and descriptive description that will appear on the credential in the wallet, for example "For High Assurance Interactions". 6. In the *Credential type* text box, enter a unique identifier for the credential type, for example `com.example.myfirstpreauthcredential`. 7. Copy and paste the following JSON into the *Claim mappings* text box: ```json title="Claim mappings" { "com.example.personaldetails.1": { "name": { "mapFrom": "claims.name", "type": "string" }, "email": { "mapFrom": "claims.email", "type": "string" } } } ``` 8. Use the *Include status* dropdown list to select **Enable**. This will enable support for changing the revocation status of the issued credentials. 9. Enter "1" in the *Months* text box in the *Validity for* panel to set the credential expiration period. 10. Select the **Create** button to create the credential configuration. Make the following request to create a simple [mDoc credentials configuration](/docs/issuance/credential-configuration/api-reference/mdocs#create-an-mdocs-credential-configuration) that includes the holder's name and e-mail: ```http title="Request" POST /v2/credentials/mobile/configurations ``` ```json title="Request body" { "type": "com.example.myfirstpreauthcredential", "expiresIn": { "months": 1 }, "claimMappings": { "com.example.personaldetails.1": { "name": { "mapFrom": "claims.name", "type": "string" }, "email": { "mapFrom": "claims.email", "type": "string" } } }, "branding": { "name": "My First Pre-Auth Credential", "description": "For High Assurance Interactions", "backgroundColor": "#a2d82dff" }, "includeStatus": true } ``` The response will include an `id` element. You will use it to create a Credential offer in the next step. ### Create a Credential offer [#create-a-credential-offer] You now have all the pieces in place and can wrap them all together to generate a [Credential offer](/docs/issuance/credential-offer/overview) and share it with the intended holder so that they know what credentials are being offered and by whom. Creating pre-authorized code flow credential offers in the MATTR Portal is for testing purposes only. The populated user ID is generated by the MATTR Portal and cannot be changed to another user later. 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Credential offer**. 3. Use the *Workflow* radio button to select **Pre-authorized code flow**. 4. Select the **Select** button. 5. Check the checkbox next to the credential configuration you created in the previous step. 6. Select the **Apply** button. 7. In the *Claims* panel, click the **Add new** button and add the following claims: * Attribute: `name`, Type: `String`, Value: `John Doe` * Attribute: `email`, Type: `String`, Value: `john.doe@example.com`\ This is an example for demonstration purposes only. In production deployments, these claims would typically be populated dynamically from a data source or provided programmatically when creating the credential offer. 8. Select the **Generate** button. 9. Copy the Transaction code that is displayed on the screen.\ In production deployments you will need to provide it to the user through a different channel (e.g., email, SMS). 10. Download the displayed QR code.\ This QR code will be used by the holder to claim the credential. Make the following request to [generate a new Pre-authorized Code flow Credential offer](/docs/issuance/pre-authorized-code/api-reference#create-credential-offer): ```http title="Request" POST /v1/openid/offers/pre-authorized ``` ```json title="Request body" { "credentials": ["707e920a-f342-443b-ae24-6946b7b5033e"], "transactionCodeConfiguration": { "inputMode": "numeric", "description": "Please enter the one-time code that was sent to you via email." }, "claims": { "name": "John Doe", "email": "john.doe@example.com" }, "expiresIn": { "minutes": 10 } } ``` * `credentials`: Populate the array with the `id` element returned in the response when you created an mDocs credentials configuration in the previous step. In this example, the claims are included directly in the credential offer. This is allowed in the Pre-authorized Code flow because the user's identity has already been verified. You can provide these claims programmatically when creating the credential offer, or you can reference a [Claims source](/docs/issuance/claims-source/overview) in your credential configuration to dynamically populate the claims in the issued credential. *Response* ```json title="Response body" { "id": "e6e5e43c-8053-464a-aca4-ca43da765c97", "uri": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Flearn.vii.au01.mattr.global%22%2C%22credentials%22%3A%5B%22b7b380be-1467-446b-8683-1d131e6532be%22%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%229K3N9UPQD-Hs5f5-gPx0fRJEmb1XTZsWszzXL024pV0%22%2C%22tx_code%22%3A%7B%22length%22%3A6%2C%22input_mode%22%3A%22numeric%22%2C%22description%22%3A%22Please%20enter%20the%20one-time%20code%20that%20was%20sent%20to%20you%20via%20email.%22%7D%7D%7D%7D", // [!code focus] "userId": "6e30dd69-c867-4279-afd3-e6619253b4a4", "expiresAt": "2025-08-25T01:39:00.523Z", "transactionCode": "763677" // [!code focus] } ``` You will need to obtain two important elements from the response: * `transactionCode` : This is the one-time code that the user will need to provide in order to claim the credential. In production deployments you will need to provide it to the user through a different channel (e.g., email, SMS, printed on paper). * `uri` : This URI can be used by a digital wallet to trigger the OID4VCI workflow. Use one of the following tools to convert the `uri` value to a QR code (make sure you use the `Plain text` option where available) and save the generated QR code as an image file. * [https://www.the-qrcode-generator.com/](https://www.the-qrcode-generator.com/) * [http://goqr.me/api/](http://goqr.me/api/) * [https://www.qr-code-generator.com/](https://www.qr-code-generator.com/) MATTR is not affiliated with any of these service providers and cannot vouch for their offerings. ### Use the MATTR GO Hold example app to claim the credential [#use-the-mattr-go-hold-example-app-to-claim-the-credential] 1. Open the GO hold example app. 2. Select **Scan**. 3. Scan the QR code generated in the previous step. 4. Review the credential offer and select **Accept**. 5. Provide the transaction code provided when you created the credential offer. 6. Follow the on-screen instructions to claim the credential. Congratulations, you just configured an end-to-end [OID4VCI Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview) to issue an mDoc into a digital wallet!!! ## What's next? [#whats-next] In this tutorial we have configured a basic [OID4VCI Pre-authorized Code flow](/docs/issuance/pre-authorized-code/overview). However, you can use MATTR VII to create more complex and rich issuance experience. Check out more resources on MATTR Learn that will enable you to: * Experiment with a different issuance flow by completing the [OID4VCI Authorization Code flow](/docs/issuance/authorization-code/tutorial) tutorial. * Configure a [Claims source](/docs/issuance/claims-source/tutorial) to retrieve data from compatible data sources and use it in the issued credential. * Apply branding to issued credentials as part of creating a [Credential configuration](/docs/issuance/credential-configuration/overview). # Credential Refresh URL: /docs/issuance/refresh/overview Credential refresh is offered as a closed beta preview feature and is not generally available yet. If you are interested in trying this feature, please [contact us](mailto:dev-support@mattr.global). ## Overview [#overview] Credential Refresh enables a wallet to claim new instances of credentials without requiring the user to authenticate again. This capability supports several use cases: * **Validity extension**: Silently refresh credentials before they expire to extend their validity period. * **Data synchronization**: Obtain credentials with the most up-to-date information as the issuer's data changes. * **Improved user experience**: Eliminate repeated authentication flows for credential re-issuance. Credential Refresh behavior depends on the holder implementation. For example, a silent refresh only occurs if the wallet’s authentication policy allows it without user interaction. Issuers should assess holder capabilities and security policies before enabling Credential Refresh. ## How it works [#how-it-works] To understand how Credential Refresh works, you need to understand the orchestration steps that occur between a wallet and an issuer in OpenID for Verifiable Credential Issuance (OID4VCI): 1. The wallet calls the Token endpoint with an Authorization Code (provided with the credential offer in Pre-authorized Code flows or from the authentication provider in Authorization Code flows). 2. If the Authorization Code is valid, the issuer returns an access token. 3. The wallet uses this access token to call the Credential endpoint, which triggers the issuance workflow and returns the issued credential to the wallet. ### Enabling Credential Refresh [#enabling-credential-refresh] MATTR VII issues a credential as refreshable when two conditions are met: 1. **The credential configuration enables refresh**: Set `refreshPolicy.enabled` to `true` in the credential configuration to define that issued credentials are refreshable. 2. **The wallet provides a DPoP token**: When calling the Token endpoint with their Authorization Code, the wallet must provide a [DPoP](https://datatracker.ietf.org/doc/html/rfc9449) (Demonstrating Proof-of-Possession) token. DPoP is critical for credential refresh because it cryptographically binds tokens to the wallet's key material. This ensures that refresh tokens can only be used by the specific wallet that originally claimed the credential, preventing token theft and replay attacks. ### The refresh flow [#the-refresh-flow] Once the above conditions are met: 1. When the wallet hits the token endpoint, the MATTR VII issuance tenant returns a refresh token alongside the issued credential. The refresh token is specific to the claiming wallet and credentials included in the credential configuration. 2. The wallet can later call the Token endpoint again with a DPoP token, but instead of providing an Authorization Code, they provide the refresh token received from the issuer. In this scenario, the issuer would also return a new refresh token alongside the newly issued access token, allowing the wallet to continue refreshing in the future. 3. The issuer validates the combination of DPoP and refresh token, and issues a new credential based on the same credential configuration. 4. The new credential includes the most up-to-date information available based on: * The claims mapping in the credential configuration. * Validity dates. * Any information stored in configured claim sources. * In Authorization Code flows, any updated information from the authentication provider is not available during refresh. * Any ephemeral claims passed during the original issuance flow would also not be available upon refresh. Refresh tokens remain valid until revoked. However, if the credential configuration is changed to no longer support refresh, credential refresh would fail even if the refresh token has not been revoked. ### Integrity Protection [#integrity-protection] To further protect the integrity of requests to endpoints that use DPoP proof tokens (such as the Token endpoint and Credential endpoint), MATTR VII supports validating the content digest of the HTTP request payload through the DPoP proof token. When making requests to these endpoints, wallets can include an optional `htcd` (HTTP Content Digest) claim in their DPoP proof token. This claim provides cryptographic verification that the request payload has not been tampered with during transmission. When the optional `htcd` claim is provided: * The claim MUST contain the content digest value of the HTTP request payload. * The value MUST align with the [HTTP Content-Digest Header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Digest) format, i.e. `sha-256=:${base64(sha256(requestBody))}=:` * If a `Content-Digest` header is also present in the HTTP request, MATTR VII will compare both values to ensure consistency. The following is in an example of a DPoP proof token with the `htcd` claim: ```json title="DPoP Proof Token" { "typ":"dpop+jwt", "alg":"ES256", "jwk": { "kty":"EC", "x":"l8tFrhx-34tV3hRICRDY9zCkDlpBhF42UQUfWVAWBFs", "y":"9VE4jf_Ok_o64zbTTlcuNJajHmt6v9TDVrU0CdvGRDA", "crv":"P-256" } } . { "jti":"-BwC3ESc6acc2lTc", "htm":"POST", "htu":"https://server.example.com/token", "htcd": "sha-256=:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=:", "iat":1562262616 } ``` When MATTR VII receives a refresh request with a DPoP proof token containing the `htcd` claim, it will validate the content digest against the actual request payload. If there is a mismatch, the request will be rejected to ensure the integrity of the credential refresh process. # Integrating mDocs revocation into your workflow URL: /docs/issuance/revocation/guide ## Overview [#overview] This guide walks you through integrating mDocs revocation into your issuance workflow. It covers the end-to-end process of issuing revocable credentials, tracking them, and revoking them when needed. ## Prerequisites [#prerequisites] * An active MATTR VII tenant. If you don't have one, complete the [sign up form](/docs/resources/get-started) and [create a tenant](/docs/platform-management/portal#creating-a-tenant). * Familiarity with the [Revocation overview](/docs/issuance/revocation/overview), which explains the underlying concepts such as status lists, status values, and status list tokens. * Familiarity with at least one OID4VCI issuance flow: * [Pre-Authorized Code flow](/docs/issuance/pre-authorized-code/overview) * [Authorization Code flow](/docs/issuance/authorization-code/overview) ## User journey [#user-journey] The following diagram illustrates the revocation journey for issuers, holders, and verifiers: 1. **Issue a revocable credential**: The issuer's backend system creates a credential offer in MATTR VII for an existing database record and stores the correlation data. The backend shares the offer with the holder, who claims it. MATTR VII issues the credential with its status tracked in a publicly available status list, and sends a webhook with the `credentialId` back to the issuer's backend, which stores it against the database record. 2. **Database record changes**: The issuer's backend system detects a change in the database record that requires revoking the associated credential (such as a permit expiration or license suspension). 3. **Revoke the credential**: The issuer's backend retrieves the stored `credentialId` from the database record and calls MATTR VII to update the credential status to `invalid`. MATTR VII updates the status list. 4. **Holder checks credential status**: When the holder's wallet checks the credential status with MATTR VII, it receives the updated status showing the credential is revoked. 5. **Verifier checks credential status**: When a verifier checks the revoked credential, MATTR VII reports the revoked status, verification fails, and the verifier is informed of the revocation. ## Integrating revocation into your workflow [#integrating-revocation-into-your-workflow] Integrating revocation into your workflow involves three main steps: 1. Enable revocation in your credential configuration to ensure issued credentials are tracked in a status list. 2. Track issued credentials by capturing their `credentialId` through webhooks or API queries. 3. Revoke credentials when necessary by updating their status to `invalid`. ## Step 1: Enable revocation in your credential configuration [#step-1-enable-revocation-in-your-credential-configuration] Issued mDocs are not revocable by default. To issue revocable mDocs, you must manually adjust the credential configuration so that every credential issued with this configuration will be added to a status list, enabling you to change its status at a later date. Enabling revocation does not affect the issuance workflow or how the credential appears to the holder. The only difference is that the issued mDoc will include a `status` reference in its MSO payload, pointing to the status list managed by your tenant and the index of the credential within that list. Enabling revocation can be performed via the Portal or API when creating or updating an mDoc credential configuration: 1. In the navigation panel, expand **Credential Issuance** and select **mDocs**. 2. Select an existing mDoc configuration. 3. Use the *Include status* radio button to select **Enable**. 4. **Save** the configuration. When creating or updating an mDoc credential configuration, set `includeStatus` to `true` in the request body: ```http title="Request" POST /v2/credentials/mobile/configurations ``` ```json title="Request body" { "type": "org.iso.18013.5.1.mDL", "includeStatus": true, // [!code focus] "claimMappings": { // Your claim mappings } // Other configuration fields } ``` See the [API reference](/docs/api-reference/platform/mdoc-credentials-configuration/createMobileCredentialConfiguration) for the full request schema. ## Step 2: Track revocable credentials [#step-2-track-revocable-credentials] To revoke a credential, you will need its `credentialId`. This identifier is generated when the credential is issued, so you need a strategy to capture and store it. The recommended approach is to configure a [webhook](/docs/platform-management/webhooks-overview) that notifies your backend when a credential is issued. ### Configure a webhook [#configure-a-webhook] Set up a webhook to receive notifications whenever a credential is claimed. The webhook payload includes the `credentialId` and other metadata you can use to associate the credential with a user or record in your system. This can be done via the Portal or API: 1. In the navigation panel, expand **Platform Management** and select **Webhooks**. 2. Click **Create new**. 3. Select the **OpenID credential issued - Summary** event. 4. Enter the HTTPS endpoint URL where you want to receive the webhook events. 5. Select **Create**. Make a request to [create a webhook](/docs/api-reference/platform/webhooks/createWebhook): ```http title="Request" POST /v1/webhooks ``` ```json title="Request body" { "events": ["OpenIdCredentialIssuedSummary"], "url": "https://your-backend.example.com/webhooks/credential-issued" } ``` Webhook events require verification and proper handling to ensure security and reliability. The steps above focus on the webhook configuration relevant to revocation workflows. For complete details on verifying webhook signatures, handling retries, and implementing idempotent handlers, refer to the [Webhooks overview](/docs/platform-management/webhooks-overview), [Webhooks guide](/docs/platform-management/webhooks-guide), and [Webhooks tutorial](/docs/platform-management/webhooks-tutorial). ### Handle the webhook payload [#handle-the-webhook-payload] When a credential is issued, MATTR VII sends a webhook notification with the following structure: ```json title="OpenIdCredentialIssuedSummary payload" { "format": "mso_mdoc", "userId": "7382276d-ef75-4d17-8fb0-1d3aec4647ab", // [!code focus] "credentialProfile": "mobile", "credentialConfigurationId": "1d8c7ad7-84ce-4519-8365-7af986e4ee0e", "credentialOfferId": "3b4f5cf6-4069-4c51-bbed-8165b4f9a889", // [!code focus] "credentialId": "9613ac5e-a0ba-4512-ba0b-90e91b2744bc", // [!code focus] "userClaims": { // [!code focus] "externalUserId": "student-12345" // [!code focus] } // [!code focus] } ``` Key fields for revocation tracking: * `credentialId`: The unique identifier of the issued credential, which you will use to revoke this credential later. * `credentialOfferId`: The identifier of the credential offer that initiated this issuance. Only present for Pre-Authorized Code flows. * `userId`: The MATTR VII User ID associated with this credential. * `userClaims`: Claims persisted on the MATTR VII user object, which can include external identifiers you set up during the issuance workflow. ### Correlate credentials with your records [#correlate-credentials-with-your-records] To revoke a credential, you need to identify which credential to revoke. This requires correlating an issued credential with either: * **A specific user**: When you need to revoke all credentials issued to a particular individual (for example, when an employee leaves your organization). * **A specific record**: When you need to revoke a credential tied to a particular data record in your system (for example, when a license expires or a qualification changes). Accurate correlation ensures you revoke the correct credential. The approach differs based on the issuance flow you use and what you are correlating to (user vs record). #### Pre-Authorized Code flow [#pre-authorized-code-flow] A Pre-Authorized Code credential offer can only be claimed **once**. Once the holder successfully claims the credential, the pre-authorized code is consumed and the offer becomes invalid. This one-to-one relationship between offer and credential makes correlation straightforward. In Pre-Authorized Code flows, you control the credential offer creation and can choose between two correlation approaches depending on what you need to associate the credential with. ##### Correlate by offer ID (for database records) [#correlate-by-offer-id-for-database-records] Use this approach when you need to associate credentials with specific database records (such as work permits, licenses, student enrollments, or employee records). Each credential represents a specific record in your system, and you need to revoke the credential when that record changes. **The typical workflow:** 1. **Create a credential offer based on an existing database record**: When a record in your system requires a credential (for example, a newly approved work permit or an issued driver's license), use MATTR VII to create a credential offer for that record. 2. **Capture and store the offer ID**: The response includes an offer `id`. Store this identifier against the original record in your database. 3. **Handle the credential issuance webhook**: When the credential is claimed, your webhook handler receives an event that includes both the `credentialOfferId` and the `credentialId`. Use the `credentialOfferId` to locate the relevant record in your database, then store the `credentialId` against that same record. 4. **Revoke when the record changes**: When changes to your database require revoking the credential (such as a permit expiration, license suspension, or record invalidation), retrieve the stored `credentialId` from your database record and use it to revoke the credential. **Create a credential offer for a database record** When you create a Pre-Authorized Code credential offer, the response includes an `id` field. Store this identifier alongside the corresponding record in your system. ```http title="Request" POST /v1/openid/offers/pre-authorized ``` ```json title="Request body" { "credentials": [ "946c4d4a-289b-4d14-8082-41b6bf749c35" ], "claims": {}, "expiresIn": { "minutes": 5, "seconds": 0 } } ``` ```json title="Response" { "id": "8241400f-de3b-42c5-ad7c-8a380039e796", // [!code focus] "uri": "openid-credential-offer://..." } ``` Store the `id` value and map it to the relevant record in your database. **Handle the webhook and store the credential ID** When your webhook handler receives the `OpenIdCredentialIssuedSummary` event, use the `credentialOfferId` field to locate the original record in your database. Then store the `credentialId` against that record so you can revoke the credential if the underlying record changes in the future. ```json title="Webhook payload" { "credentialOfferId": "8241400f-de3b-42c5-ad7c-8a380039e796", // [!code focus] "credentialId": "9613ac5e-a0ba-4512-ba0b-90e91b2744bc", // [!code focus] "userId": "19bf8183-a9dc-41cd-9336-1f5d19f1ae3d", "userClaims": {} } ``` ##### Correlate by external user ID (for users) [#correlate-by-external-user-id-for-users] Use this approach when you need to associate credentials with specific users in your system. Each credential is tied to a user, and you need to revoke all credentials when a user's status changes (such as when an employee leaves or a membership expires). **The typical workflow:** 1. **Ensure the user exists in MATTR VII**: Before creating the credential offer, [search for or create a user](/docs/issuance/users/guide#before-issuing-the-credential) in MATTR VII. Store an external identifier (such as your system's user ID) in the user's `claims` object. 2. **Create a credential offer associated with the user**: Include the MATTR VII `userId` in the credential offer request. This associates the offer with the user. 3. **Handle the credential issuance webhook**: When the credential is claimed, your webhook handler receives an event that includes the `credentialId` and `userClaims` (which contain your external user identifier). Use the `userClaims.externalUserId` to locate the user in your system, then store the `credentialId` against that user. 4. **Revoke when the user's status changes**: When a user's status changes and requires credential revocation (such as employment termination or membership expiration), retrieve all stored `credentialId` values for that user and revoke them. **Create a user with an external identifier** Before creating the credential offer, ensure a MATTR VII user exists with your external user identifier stored in their claims: ```http title="Request" POST /v1/users ``` ```json title="Request body" { "claims": { "externalUserId": "employee-12345" // [!code focus] } } ``` ```json title="Response" { "id": "19bf8183-a9dc-41cd-9336-1f5d19f1ae3d", // [!code focus] "claims": { "externalUserId": "employee-12345" } } ``` Store the MATTR VII user `id` for use in the next step. **Create a credential offer associated with the user** When creating the credential offer, include the `userId` to associate it with the user: ```http title="Request" POST /v1/openid/offers/pre-authorized ``` ```json title="Request body" { "credentials": [ "946c4d4a-289b-4d14-8082-41b6bf749c35" ], "userId": "19bf8183-a9dc-41cd-9336-1f5d19f1ae3d", // [!code focus] "claims": {}, "expiresIn": { "minutes": 5, "seconds": 0 } } ``` **Handle the webhook and store the credential ID** When your webhook handler receives the `OpenIdCredentialIssuedSummary` event, use the `userClaims.externalUserId` field to locate the user in your database. Then store the `credentialId` against that user so you can revoke their credentials if their status changes. ```json title="Webhook payload" { "credentialOfferId": "8241400f-de3b-42c5-ad7c-8a380039e796", "credentialId": "9613ac5e-a0ba-4512-ba0b-90e91b2744bc", // [!code focus] "userId": "19bf8183-a9dc-41cd-9336-1f5d19f1ae3d", "userClaims": { // [!code focus] "externalUserId": "employee-12345" // [!code focus] } // [!code focus] } ``` You can also retrieve all credentials issued to a specific user via the [Retrieve all user credentials data](/docs/api-reference/platform/users/getUserCredentials) endpoint, which returns credential metadata including the `credentialId`, `status`, and `offerId`. #### Authorization Code flow [#authorization-code-flow] In Authorization Code flows, you don't know who will claim the credential until they authenticate. The user's identity is determined by the authentication provider during the issuance process. An Authorization Code credential offer can be claimed **multiple times** by **different users**. Each user authenticates independently and receives their own credential. This means you cannot associate the offer with a specific credential or user in advance. Instead, you must correlate the issued credential with your records **after** the credential is issued, using information from the webhook event. In Authorization Code flows, correlation always happens post-issuance. You can choose between two approaches depending on what you need to associate the credential with. ##### Correlate to database records via custom claims [#correlate-to-database-records-via-custom-claims] Use this approach when you need to associate credentials with specific database records rather than users directly. For example, when a credential represents a license or permit that exists as a separate record in your system, and multiple people might authenticate to claim the same type of credential for their own individual records. In this scenario, you can use custom claims or interaction hooks to include a database record identifier during the authentication flow, which will be persisted and can be used for correlation. **The typical workflow:** 1. **Configure authentication to capture record identifiers**: Set up your authentication provider or an [interaction hook](/docs/issuance/authorization-code/interaction-hook/overview) to capture a database record identifier during the authentication flow. This could be passed as a request parameter or retrieved from your systems based on the authenticated user. 2. **Persist the record identifier**: Use `claimsToPersist` to store the database record identifier in the MATTR VII user record, or pass it through the issuance flow. 3. **Handle the credential issuance webhook**: When a credential is claimed, your webhook handler receives an event. Retrieve the database record identifier from the user's claims or from custom claims in the webhook payload, then store the `credentialId` against that database record. 4. **Revoke when the record changes**: When changes to your database require revoking the credential (such as a license expiration or permit suspension), retrieve the stored `credentialId` from your database record and use it to revoke the credential. This approach typically requires additional integration work such as configuring interaction hooks or passing request parameters through your authentication flow. For most use cases where you want to associate credentials with database records, the Pre-Authorized Code flow provides a simpler and more direct solution. ##### Correlate to users in your system [#correlate-to-users-in-your-system] Use this approach when you need to associate credentials with specific users. Each authenticated user receives a credential that should be tied to their user record in your system. You need to revoke credentials when a user's status changes (such as when a membership expires or an employee leaves). **The typical workflow:** 1. **Configure your authentication provider to persist identifiers**: Set up your authentication provider to persist non-PII identifiers (such as the `sub` claim or an external user ID) to the MATTR VII user record. These identifiers will be used to correlate authenticated users with user records in your database. 2. **Create a credential offer for authenticated users**: Create an Authorization Code credential offer that any eligible user can claim after authenticating. You don't know in advance who will claim it or when. 3. **Handle the credential issuance webhook**: When a credential is claimed, your webhook handler receives an event that includes the `userId` and `credentialId`. Retrieve the MATTR VII user record using the `userId` to access the persisted identifier (such as `authenticationProvider.subjectId`). Use this identifier to locate the corresponding user in your database, then store the `credentialId` against that user. 4. **Revoke when the user's status changes**: When a user's status changes and requires credential revocation (such as membership expiration or employment termination), retrieve all stored `credentialId` values for that user and revoke them. **Configure your authentication provider to persist identifiers** When using an authentication provider, configure it to persist identifying claims from the identity provider to the MATTR VII user record. This enables you to later correlate MATTR VII users with user records in your external systems. Avoid persisting Personally Identifiable Information (PII) such as email addresses or phone numbers in MATTR VII user claims. Instead, persist external identifiers (such as user IDs or account numbers from your system) or the authentication provider's `sub` claim. These non-sensitive identifiers can be correlated internally with your systems without exposing PII. Set the `claimsToPersist` field in your [Authentication provider configuration](/docs/api-reference/platform/authentication-provider/createAuthenticationProvider) to include relevant user identifiers: ```json title="Authentication provider configuration (partial)" { "claimsToPersist": ["sub"] } ``` When a user authenticates and claims a credential, MATTR VII persists the specified claims from the identity provider into the user's `claims` object. The `authenticationProvider.subjectId` is also automatically stored with the user record. **Handle the webhook and store the credential ID** When your webhook handler receives the `OpenIdCredentialIssuedSummary` event, use the `userId` to look up the MATTR VII user record and retrieve the persisted identifier: ```http title="Request" GET /v1/users/{userId} ``` ```json title="Response" { "id": "7382276d-ef75-4d17-8fb0-1d3aec4647ab", "claims": { "sub": "auth0|abc123" }, "authenticationProvider": { "subjectId": "auth0|abc123" } } ``` Use the `sub` or `authenticationProvider.subjectId` value to locate the corresponding user in your database, then store the `credentialId` from the webhook payload against that user so you can revoke their credentials if their status changes. ```json title="Webhook payload" { "credentialId": "9613ac5e-a0ba-4512-ba0b-90e91b2744bc", // [!code focus] "userId": "7382276d-ef75-4d17-8fb0-1d3aec4647ab", "credentialConfigurationId": "1d8c7ad7-84ce-4519-8365-7af986e4ee0e" } ``` ### Correlation strategy summary [#correlation-strategy-summary] | Issuance flow | Correlation method | What it associates | When to use | | ------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------- | | Pre-Authorized Code | Match `credentialOfferId` from webhook to stored offer ID | Database record (permit, license, enrollment) | Each credential represents a specific record that can change independently of the user | | Pre-Authorized Code | Match `userClaims.externalUserId` from webhook to user in your system | Specific user | Each credential is tied to a user; revoke when user status changes (termination, expiration) | | Authorization Code | Look up `userId`, retrieve `authenticationProvider.subjectId`, match to user in your system | Specific user | User authenticates to claim; credentials tied to authenticated user identity | | Authorization Code | Use custom claims or interaction hooks to capture database record identifier | Database record (via custom integration) | Advanced: Credential represents a record, requires additional integration with authentication flow | | Both | Query [user credentials endpoint](/docs/api-reference/platform/users/getUserCredentials) to list all credentials for a user | Specific user | You want to find all credentials associated with a specific user | ## Step 3: Revoke a credential [#step-3-revoke-a-credential] Once you have the `credentialId` of the credential you want to revoke, use MATTR VII to update its status. Currently this is only available via the API: Make a request to [update the mDoc status](/docs/api-reference/platform/mdocs-status/postCredentialStatus): ```http title="Request" POST /v2/credentials/mobile/{credentialId}/status ``` ```json title="Request body" { "status": "invalid" } ``` * `credentialId`: Replace with the credential identifier you captured from the webhook or the user credentials endpoint. ```json title="Response" { "status": "invalid" } ``` Setting the status to `invalid` is **irreversible**. Once a credential is invalidated, it cannot be restored to a valid state. Use this only when you intend to permanently revoke the credential. ## What holders and verifiers see [#what-holders-and-verifiers-see] ### Holders [#holders] Once a credential is revoked, the holder's wallet should reflect the status change. The credential remains in the wallet but is marked as revoked or invalid, depending on the wallet implementation. Here is an example of how a revoked credential might appear in MATTR GO Hold:

GO Hold Revoked Credential GO Hold Revocation Status
### Verifiers [#verifiers] When a verifier attempts to verify a revoked credential, the verification process checks the credential's status against the status list. If the credential has been revoked: * Verification **fails**. * The verifier is informed that the credential has been **revoked**. * Credential details (such as name, portrait, or address) can still be shown, depending on the verifier's implementation. MATTR Verifier SDKs will show the details but indicate the revoked status clearly. However, third-party verifiers that you do not control may choose to hide all credential details when a credential is revoked. Here is an example of how a revoked credential might appear in MATTR GO Verify:
GO Verify Revoked Credential
MATTR platforms check revocation status in a privacy-preserving manner by retrieving publicly available status lists that hold revocation information for multiple credentials. This means the issuer cannot determine which specific credentials are being checked by the verifier or for what purpose. As mDocs revocation is an emerging standard, relying parties that have not implemented revocation checks might still assess a presented mDoc as valid, even when it has been revoked by the issuer. ## Revocation propagation timing [#revocation-propagation-timing] When you revoke a credential, the status change is not immediately visible to all holders and verifiers. The time it takes for revocation to propagate depends on the **Status list configuration** for the credential's `docType`, specifically the `timeToLiveDuration` (TTL) and `expiryDuration` (EXP) values. * **TTL (Time To Live)**: The recommended duration a relying party should cache a status list token before retrieving a new one. This is a guideline; relying parties should respect it but are not strictly enforced to do so. * **EXP (Expiry)**: The maximum duration a status list token remains valid. After this time, a relying party **must** retrieve a new token. They cannot use an expired token to check credential status. The default values are: | Setting | Default | Minimum | Maximum | | ------- | ------- | ------- | ------------------------------- | | TTL | 1 day | N/A | Cannot exceed EXP | | EXP | 1 week | N/A | Cannot exceed the IACA validity | ### How status list caching works [#how-status-list-caching-works] Status list tokens are cached to improve performance and enable offline verification. The following diagram illustrates how TTL and EXP affect the revocation propagation timeline: Status list tokens are cached to improve performance and enable offline verification: 1. **You revoke a credential**: The status list is updated immediately on your MATTR VII tenant. If the current status list token is cached, the cache is cleared and a new token is signed. 2. **Holder or verifier requests the status list**: If they have a locally cached copy of the status list token, they use it until the TTL expires. Once the TTL expires, they retrieve a fresh token. 3. **Fresh token reflects the revocation**: The newly retrieved status list token includes the updated status for the revoked credential. **Key points from the diagram:** * The TTL (Time To Live) determines when compliant wallets and verifiers should retrieve a fresh status list token. In this example, with a 1-day TTL, the revocation becomes visible when the wallet fetches a new token on Day 4 (24 hours after the previous fetch). * If a wallet or verifier does not respect the TTL recommendation, they may continue using the cached token until it reaches its EXP (Expiry). In this example, with a 1-week EXP, the revocation would not be visible until Day 7 at the latest. * MATTR SDKs respect TTL values, but you cannot guarantee the behavior of third-party wallets and verifiers that you do not control. ### Balancing freshness and offline capability [#balancing-freshness-and-offline-capability] Status list caching represents a deliberate trade-off between two important requirements: **Ensuring up-to-date credential status:** * Shorter TTL and EXP values mean wallets and verifiers fetch fresh status list tokens more frequently. * This reduces the time between when you revoke a credential and when holders and verifiers see the updated status. * However, it requires more network requests and means wallets and verifiers must be online more frequently to verify credentials. **Supporting offline verification:** * Longer TTL and EXP values enable wallets and verifiers to cache status list tokens for extended periods. * This allows credential verification to continue in offline or low-connectivity environments without requiring constant network access. * However, it increases the time between when you revoke a credential and when the revocation becomes visible to holders and verifiers who already have a cached token. **Choosing the right balance:** The optimal TTL and EXP values depend on your use case: * **High-security credentials** (e.g., employee access badges, time-sensitive permits): Use shorter TTL/EXP values to ensure revocations propagate quickly. Accept that verification may fail in offline scenarios. * **Offline-first credentials** (e.g., travel documents, educational certificates): Use longer TTL/EXP values to maximize offline verification capability. Accept that revocations may take longer to propagate. * **Balanced approach** (default): TTL of 1 day and EXP of 1 week provides reasonable freshness while still supporting some offline verification scenarios. If your verification workflows predominantly occur in controlled environments with reliable network connectivity (such as border control or workplace access), you can prioritize freshness with shorter TTL/EXP values. If your credentials are frequently verified in remote or low-connectivity environments (such as rural areas or international travel), prioritize offline capability with longer values. ### Configuring TTL and EXP [#configuring-ttl-and-exp] You can adjust these values using a [Status list configuration](/docs/api-reference/platform/status-list-configuration/createStatusListConfiguration). Status list configurations are unique per `docType` per tenant. When you update the status list configuration (e.g. change TTL from 24 hours to 2 hours), holders and verifiers who already have a cached token will continue using the old values until their cached token's TTL expires or the token reaches its EXP. Currently, status list configurations can only be managed via the API: ```http title="Request" POST /v2/credentials/mobile/status-lists/configurations ``` ```json title="Request body" { "docType": "org.iso.18013.5.1.mDL", "timeToLiveDuration": { "hours": 2 }, "expiryDuration": { "hours": 12 } } ``` To update an existing configuration: ```http title="Request" PUT /v2/credentials/mobile/status-lists/configurations/{id} ``` ```json title="Request body" { "timeToLiveDuration": { "hours": 2 }, "expiryDuration": { "hours": 12 } } ``` ### Propagation scenarios [#propagation-scenarios] The following scenarios illustrate how different TTL and EXP configurations affect when revocation becomes visible. #### Scenario 1: Fast propagation [#scenario-1-fast-propagation] * **TTL**: 1 hour * **EXP**: 6 hours In this scenario, relying parties retrieve a fresh status list token at least every hour. If you revoke a credential, a compliant relying party will see the revocation within 1 hour or less. This is suitable when timely revocation is critical, but results in more frequent requests to your tenant's status list endpoint. #### Scenario 2: Standard propagation (default) [#scenario-2-standard-propagation-default] * **TTL**: 1 day * **EXP**: 1 week Relying parties cache the status list token for up to 1 day before retrieving a new one. If you revoke a credential, it may take up to 24 hours for the revocation to propagate across all relying parties. Even if a relying party ignores the TTL, the token expires after 1 week, forcing a refresh. #### Scenario 3: Offline-optimized [#scenario-3-offline-optimized] * **TTL**: 3 days * **EXP**: 2 weeks This configuration is suited for scenarios where verifiers operate frequently in offline environments and need to cache status list tokens for extended periods. The trade-off is that revocation may take up to 3 days to propagate under normal conditions. ## Related resources [#related-resources] * [Revocation overview](/docs/issuance/revocation/overview): Conceptual overview of how revocation works for mDocs and CWT credentials. * [Revocation tutorial](/docs/issuance/revocation/tutorial): Step-by-step walkthrough of issuing, checking, and revoking a credential. * [Users guide](/docs/issuance/users/guide): How to manage user associations throughout the credential issuance lifecycle. * [Webhooks guide](/docs/platform-management/webhooks-guide): How to configure and handle webhook events. * [mDocs status API reference](/docs/issuance/revocation/api-reference/mdocs-status-update): Update and retrieve mDocs revocation status. * [Status list configuration API reference](/docs/issuance/revocation/api-reference/mdocs-status-list-configuration): Manage status list TTL and EXP settings. # Credential revocation journey pattern URL: /docs/issuance/revocation/journey-pattern This journey pattern enables issuers to update the status of issued credentials. Verifiers can check the status of a presented credential without compromising the holder’s privacy. ## Overview [#overview] * **Formats**: mDocs, CWT, Semantic CWT ## Journey flow [#journey-flow] Revocation journey pattern ## Architecture [#architecture] Revocation architecture ### Credential issuance [#credential-issuance] The issuer uses MATTR VII capabilities to issue a credential in a way that enables it to be revoked at a later date. Revocable credentials include a reference to a revocation list that reflects the most up-to-date status of credentials included in it. This does not affect the issuance workflow or how would the credential look like to the holder. ### Credential revocation [#credential-revocation] When required, the issuer can use MATTR VII capabilities to revoke the issued credential. This will update the status of the credential in the referenced status list. ### Revocation notification [#revocation-notification] The issuer can use MATTR VII capabilities or their existing communication channels to notify the holder that the credential has been revoked. ### Verifying a revoked credential [#verifying-a-revoked-credential] When a relying party attempts to verify a revoked credential, they can use MATTR VII or MATTR Pi capabilities that check the revocation status of the credential as part of the verification process. If the credential is found to be revoked, verification will fail and the verifier will be notified of the relevant failure reason. MATTR platforms check the revocation status in a privacy preserving manner by checking the publicly accessible revocation lists which hold the revocation information for multiple credentials. This way the issuer cannot tell what credentials were actually checked by the relying party and for what purpose. # Revocation URL: /docs/issuance/revocation/overview ## Overview [#overview] Issuers might need to invalidate a previously issued digital credential before its expiry date. This might be required when a credential has been compromised, its status and/or claims have changed, or in cases when a credential is issued in error. You can use MATTR VII to change the status of issued credentials if you ever need to revoke or suspend them for any reason. Verifiers can then obtain the status of presented credentials in a way that preserves holders’ privacy. The implementation differs based on your chosen credential format: * [mDocs](#mdocs) * [CWT credentials](#cwt) ## mDocs [#mdocs] MATTR's implementation of mDocs revocation is based on Draft 14 of the IETF [Token Status List](https://drafts.oauth.net/draft-ietf-oauth-status-list/draft-ietf-oauth-status-list.html) specification. A revocable mDoc includes a reference to a status list which is managed by the issuer. The list contains the revocation status of multiple credentials, and each credential references the index of its status within a specific status list. Status lists are automatically created and managed when you use your MATTR VII tenant to issue revocable mDocs. They are publicly available and mainly consumed by external verifiers to check the status of presented mDocs. When a relying party retrieves a status list, the issuer cannot tell what specific mDoc they are checking the status for. This means the issuer does not know how often or to whom an mDoc is being presented, maintaining holder's privacy. As mDocs revocation is an emerging standard, relying parties are not enforced to implement it. As a result, relying parties which have not implemented revocation checks might assess a presented mDoc as valid, even when it was revoked by the issuer. ### Status values [#status-values] The following status values are available for revocable mDocs: * **Valid**: The mDoc is valid. Valid mDocs can be updated to `Invalid`. * **Invalid**: The mDoc is revoked. This state is irreversible - once an mDoc status was updated to `Invalid`, it cannot be updated to any other status. This value should be used for permanently revoking an mDoc. * **Suspended**: ⚠️ **Deprecated** ⚠️ The mDoc is temporarily revoked. Suspended mDocs can be updated to `Valid` or `Invalid`. This value should be used for temporarily revoking an mDoc. ### Revocable mDocs [#revocable-mdocs] By default signed mDocs are not revocable. To issue revocable mDocs and enable changing their status at a later date, the request payload must set `includeStatus` to `true`. This can be configured when creating an [mDoc credential configuration](/docs/issuance/credential-configuration/overview). This results in the following: * The mDoc status is added to a status list. * The issued mDoc includes a `status` object in its [MSO payload](/docs/concepts/mdocs/structure-to-function) that references this status list: ``` "status": { "status_list": { "uri": "https://learn.vii.au01.mattr.global/v2/credentials/mobile/status-lists/f331c9be-f526-4577-bbac-ae93d6228f7a/token", "idx": 0 } } ``` * `status` : This field includes the information regarding the revocation status of this mDoc. It is added to the MSO payload of the mDoc when `includeStatus` is set to `true` in the credential configuration. Legacy credentials issued before the adoption of Draft 14 of the Token Status List specification use the `_status` field name which is now deprecated. The `status` field includes the following properties: * `status_list` : References the status list that holds the status information for this mDoc. * `uri` : Relying parties can use this publicly available endpoint to retrieve the status list token. * `idx` : Indicates the index of the mDoc status within the referenced status list. ### Status list [#status-list] MATTR VII automatically creates and manages status lists when revocable mDocs are signed. A status list is a bit string where each credential status is mapped to a position in the list. The number of bits per credential depends on the tenant configuration: **1-bit status lists** (latest specification as per Draft 14 of the Token Status List specification): * Each credential occupies **one bit**: * `0`: Valid * `1`: Invalid * Each list can hold the status of up to **500,000** individual mDocs. **2-bit status lists** (legacy format, deprecated with the adoption of Draft 14 of the Token Status List specification): * Each credential occupies **two bits**: * `00`: Valid * `01`: Invalid * `10`: Suspended * Each list can hold the status of up to **500,000** individual mDocs. The bit configuration is specified in the status list token's `bits` field and is determined by your tenant's configuration. ### Status list tokens [#status-list-tokens] When a relying party attempts to retrieve a status list the response does not include the raw status list, but rather a signed version which is referred to as a status list token. This is a [CBOR Web Token](https://datatracker.ietf.org/doc/html/rfc8392). #### Token header [#token-header] The token header includes a `typ` (type) field that indicates the token's format: * **Latest specification**: `application/statuslist+cwt` * **Legacy format**: `mattr-statuslist+cwt` Relying parties can use this field to automatically detect and handle both formats. #### Token payload [#token-payload] The token payload includes the following information: * `iat` (CBOR claim 6): Timestamp when the status list token was signed. * `sub` (CBOR claim 2): The URI that was used to retrieve the token. This must match the `uri` from the mDoc `status` object. * `exp` (CBOR claim 4): Timestamp at which the token expires and the relying party can no longer use it to check the mDoc status. This prevents relying parties from using locally cached tokens in their verification workflows for too long, and forces them to retrieve a new token with the most up-to-date mDocs status. Defaults to one week from when the token was signed but can be adjusted using a [Status list configuration](#status-list-configuration). Regardless of the Status list configuration, `exp` can never exceed the validity of the IACA used to sign the Status list token. * `ttl` (CBOR claim 65534 in latest specification, -65539 in legacy): Time To Live - Recommended duration (in seconds) the relying party should use this token before retrieving a new one. This guides best practices for relying parties to ensure they are using the most up-to-date mDocs status in their verification workflows. Defaults to one day but can be adjusted using a [Status list configuration](#status-list-configuration). Regardless of the Status list configuration, `ttl` can never exceed `exp`. * `status_list` (CBOR claim 65533 in latest specification, -65538 in legacy): * `bits`: Details the number of bits each mDoc occupies in the list (1 for latest specification, 2 for legacy). * `lst`: Compressed zlib byte string containing the status of all mDocs included in this status list. When CBOR claim numbers differ between formats, relying parties must detect the format based on the token header `typ` field and parse the payload accordingly. This is automatically handled by MATTR Holder and Verifier SDKs. #### Token signature [#token-signature] Status list tokens are signed by Status list signers. By default, MATTR VII automatically creates and manages these signers. If you use [unmanaged certificates](/docs/concepts/chain-of-trust#external-certificates), you are responsible for managing your own Status list signers and their certificates. #### Caching [#caching] Status list tokens are automatically managed by MATTR VII and cached for improved performance. Once a new Status list token is signed, it can be cached for a minimum time of 1 minute and up to a maximum of 24 hours. The cache is cleared and a new Status list token is signed in the following scenarios: * The Status list token has reached its maximal caching limit (24 hours). * The referenced [Status list configuration](#status-list-configuration) is updated. * The status list is updated (e.g. the status of an mDoc that is included in the status list is changed). #### When TTL configuration changes take effect [#when-ttl-configuration-changes-take-effect] If you update the status list configuration to change the TTL or EXP values, these changes apply to the entire status list the next time it is signed. **The status list token is re-signed in the following scenarios:** * The current TTL expires * A credential on the list is updated (e.g., revoked or suspended) * A new credential is added to the list * The status list configuration is updated **On the holder or verifier side**, the updated status list token (with the new TTL/expiry values) is retrieved when: * The current cached status list token's TTL expires * The holder/verifier requests the status list after you have signed the new version **Example scenario**: If you issue a credential with a 24-hour TTL, then immediately update the status list configuration to use a 2-hour TTL, holders and verifiers will continue using the 24-hour TTL until the status list token is re-signed. This happens when the original 24-hour TTL expires, or when any credential on that list is updated, or when a new credential is added. Once the new token is signed, subsequent retrievals will use the new 2-hour TTL. #### IACA deletion [#iaca-deletion] Status list tokens must be signed using the same [IACA](/docs/issuance/certificates/overview#issuing-authority-certificate-authority-iaca) that was used to sign the mDocs that are included in the status list. If this IACA is deleted, all status lists signed by it are deleted as well, and relying parties will not be able to retrieve new Status list tokens that include the status of these mDocs. When attempting to manually retrieve the revocation status of these mDocs, the returned status will be `Unknown`. When an IACA is deleted, relying parties can still use locally cached copies of Status list token signed by this IACA until they reach their set `exp`. ### Status list configuration [#status-list-configuration] Issuers can optionally create status list configurations to manually adjust the Status list `ttl` and `exp` values for a specific `docType`. This enables issuers to adjust the duration for which relying parties can use a status list token before being forced to retrieve a new copy. Status list configurations are unique per `docType` per tenant and are referenced by the Status lists that include mDocs of this `docType`. If no Status list configuration is available, MATTR VII will automatically create one when a new Status list is created based on the default `ttl` (one day) and `exp` (one week) values. ### Status list distribution [#status-list-distribution] Any relying party is able to retrieve an Issuer's status lists and cache them locally to support offline verification workflows. This is achieved by making a GET request to the [distribution endpoint](/docs/issuance/revocation/api-reference/mdocs-status-list-retrieval#status-list-distribution). This endpoint is public and does not require any authentication. It is referenced in the issuer's IACA `X509v3 extensions` element under the `1.3.6.1.4.1.61546.100` OID, as shown in the following example: ``` X509v3 extensions: 1.3.6.1.4.1.61546.100: https://learn.vii.au01.mattr.global/v2/credentials/mobile/status-lists/distribution ``` The response will include a `status_lists` array which details the URIs of the different Status list tokens that are available on this MATTR VII tenant. The response format varies based on your tenant configuration. Relying parties must support both response formats for compatibility. MATTR SDKs automatically handle both formats: **Draft 14 Specification format:** ```json title="Status lists response (Draft 14 Specification)" { "status_lists": [ "https://learn.vii.au01.mattr.global/v2/credentials/mobile/status-lists/{id}/token", "https://learn.vii.au01.mattr.global/v2/credentials/mobile/status-lists/{id2}/token" ] } ``` **Legacy format:** ```json title="Status lists response (Legacy format)" { "status_lists": [ { "uri": "https://learn.vii.au01.mattr.global/v2/credentials/mobile/status-lists/{id}/token" }, { "uri": "https://learn.vii.au01.mattr.global/v2/credentials/mobile/status-lists/{id2}/token" } ] } ``` Status list tokens signed by expired IACAs are excluded from the response. ### Status list signers [#status-list-signers] Status list tokens are signed by Status list signers. By default, MATTR VII automatically creates and manages these signers and their Status List Signer Certificates (SLSCs). If you use [unmanaged certificates](/docs/concepts/chain-of-trust#external-certificates), you are responsible for managing your own Status list signers and their certificates. Managed SLSCs may be [revoked](/docs/issuance/revocation/api-reference/mdocs-status-list-signers#revoke-a-status-list-signer) for reasons such as key compromise, role change, or decommissioning. Once revoked, the SLSC’s serial number is published in the Certificate Revocation List (CRL) referenced by its issuing IACA. Verifiers that retrieve and process the CRL referenced in the IACA must treat a revoked SLSC, and any Status lists it signed, as invalid. ### Backward compatibility [#backward-compatibility] MATTR VII maintains backward compatibility between the legacy and Draft 14 Token Status List formats to ensure existing implementations continue to function while enabling adoption of the standardized specification. #### Format selection [#format-selection] * Tenants can opt into the latest Token Status List specification via a feature flag. [Contact us](mailto:dev-support@mattr.global) to enable this for your tenant. * Legacy format continues to be supported for existing implementations. * The format is determined at credential issuance time based on tenant configuration. * Individual credentials cannot change formats after issuance. #### Key differences between formats [#key-differences-between-formats] The following table summarizes the differences between the legacy MATTR format and the latest Token Status List specification: | Aspect | Legacy Format | Latest Specification | | -------------------------------- | -------------------------------------- | ---------------------------- | | **MSO field name** | `_status` | `status` | | **Token header `typ`** | `mattr-statuslist+cwt` | `application/statuslist+cwt` | | **Status bits per credential** | 2 bits | 1 bit | | **Available status values** | Valid, Invalid, Suspended (Deprecated) | Valid, Invalid | | **CBOR payload claim numbers** | Negative keys (-65538, -65539) | Positive keys (65533, 65534) | | **Distribution response format** | Array of objects with `uri` property | Array of URI strings | | **Maximum credentials per list** | 500,000 | 500,000 | #### SDK and verifier compatibility [#sdk-and-verifier-compatibility] * **MATTR SDKs** automatically detect and handle both formats based on the token header `typ` field * **Verifiers** must support both distribution endpoint response structures * **Status list tokens** are self-describing - the `typ` header identifies the format #### Migration considerations [#migration-considerations] When a tenant opts into the latest specification: * All **new** credentials are issued using the new format * **Existing** credentials continue to use their original format * Both formats can coexist within the same tenant * Status lists are maintained separately per format to prevent conflicts ## CWT [#cwt] You can use MATTR VII to change the status of issued CWT credentials if you ever need to revoke them for any reason. Verifiers can obtain the revocation status of presented credentials in a way that preserves holders’ privacy. A revocable CWT credential points to a revocation list which is managed by the credential\`s issuer. The list contains the revocation status of many credentials, and the credential references the index of its status within a specific revocation list. Revocation lists are automatically created and managed when you use your MATTR VII tenant to issue revocable credentials. They are publicly available and mainly consumed by external verifiers. When a verifier requests a revocation list, the issuer cannot tell what credential they are checking the status for. This means the issuer does not know how often or to whom a credential is being presented, maintaining holder\`s privacy. ### `credentialStatus` object [#credentialstatus-object] When a CWT credential is set as revocable the `credentialStatus` object is added to the credential: ```json title="credentialStatus object" "status": { "index": 4, "url": "https://learn.vii.au01.mattr.global/v2/credentials/compact/revocation-lists/f91bbef3-6898-4930-bca3-cf0d4b63e939" } ``` * `index` : This indicates the index of this specific credential within the revocation list. * `url` : Every revocable credential will reference a Revocation List that is automatically created and held on the issuer's tenant. This list can be used by external verifiers to validate the credential status. The `status.url` property references the revocation list which holds the revocation status for this specific credential. # Learn how to manage the revocation status of issued credentials URL: /docs/issuance/revocation/tutorial ## Overview [#overview] In this tutorial we will explore the concept of [credential revocation](/docs/issuance/revocation/overview), a critical feature that allows issuers to invalidate previously issued credentials, ensuring the integrity and security of the system. The tutorial comprises the following steps: 1. Issue a revocable credential. 2. Obtain a credential revocation status. 3. Revoke an issued credential. 4. Attempt to verify a revoked credential. ## Prerequisites [#prerequisites] * Complete the [sign up form](/docs/resources/get-started) to get trial access to MATTR VII and the MATTR Portal, and then [Create a tenant](/docs/platform-management/portal#creating-a-tenant). * Install the MATTR GO Hold example app by following the [getting started guide](/docs/holding/go-hold/getting-started). We recommend using the MATTR VII [Postman collection](/docs/api-reference#postman-collection) in this tutorial. While this isn't an explicit prerequisite, it can really speed things up. ## Tutorial steps [#tutorial-steps] ### Issue a revocable credential [#issue-a-revocable-credential] The first thing you need to do is sign a new credential in a way that will enable you to revoke it later. This will differ slightly based on your selected credential format. 1. Follow the [Authorization Code](/docs/issuance/authorization-code/tutorial) or [Pre-authorized Code](/docs/issuance/pre-authorized-code/tutorial) tutorials to issue a credential. 2. By the end of the tutorial you should have an mDoc in your MATTR GO Hold example app. 1. Make a request of the following structure to [create a `did:web`](/docs/api-reference/platform/dids/createDid): ```http title="Request" POST /v1/dids ``` ```json title="Request body" { "method": "web", "options": { "url": "https://learn.vii.au01.mattr.global" // [!code highlight] } } ``` * `url` : Replace with your `tenant_url` provided with your [tenant details](/docs/platform-management/portal#getting-started). *Response* ```json title="Response body" { "did": "did:web:learn.vii.au01.mattr.global" // [!code focus] // Rest of DID document } ``` * `did` : We will use the value of this element to identify the credential's issuer in the next step. If your tenant has a [Custom domain](/docs/platform-management/custom-domain-overview) configured you will need to [setup a redirect](/docs/platform-management/custom-domain-guide#create-redirects-for-required-assets) for this DID document. 2. Make a request of the following structure to [create and sign a new revocable CWT credential](/docs/issuance/direct-issuance-api-reference/cwt-issuance#sign-a-cwt-credential): ```http title="Request" POST /v2/credentials/compact/sign ``` ```json title="Request body" { "payload": { "iss": "did:web:learn.vii.au01.mattr.global", // [!code highlight] "type": "Course Credential", "name": "Emma Jane Tasma", "code": "HS.278", "certificationName": "Working at Heights", "certificationLevel": "Level 4", "issuerName": "Advanced Safety Training" }, "revocable": true, // [!code highlight] "isRevoked": false // [!code highlight] } ``` * `iss` : Replace with the `did` element obtained in the previous step. * `revocable`: We set this to `true` so that this credential can be revoked later in the tutorial. * `isRevoked`: We set this to `false` so that this credential is immediately valid upon issuance. Other use cases might require you to set this to `true` to issue a credential that is revoked by default until it is activated by unrevoking. *Response* ```json title="Response body" { "id": "bKcrxojFSuSZvI5qhKInxA", // [!code highlight] "decoded": { "iss": "did:web:learn.vii.au01.mattr.global", "type": "Course Credential", "name": "Emma Jane Tasma", "code": "HS.278", "certificationName": "Working at Heights", "certificationLevel": "Level 4", "issuerName": "Advanced Safety Training", "status": { // [!code highlight] "index": 3, // [!code highlight] "url": "https://learn.vii.au01.mattr.global/v2/credentials/compact/revocation-lists/887cd140-e4d7-4518-b70f-305b23778848" // [!code highlight] }, "jti": "bKcrxojFSuSZvI5qhKInxA" }, "encoded": "CSC:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSALWVQAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQHIAACAACOFBW65LSONSSAQ3SMVSGK3TUNFQWYBA2NFLDPEDENZQW2ZLPIVWW2YJAJJQW4ZJAKRQXG3LBMRRW6ZDFMZEFGLRSG44HCY3FOJ2GSZTJMNQXI2LPNZHGC3LFOJLW64TLNFXGOIDBOQQEQZLJM5UHI43SMNSXE5DJMZUWGYLUNFXW4TDFOZSWYZ2MMV3GK3BAGRVGS43TOVSXETTBNVSXQGCBMR3GC3TDMVSCAU3BMZSXI6JAKRZGC2LONFXGOZTFPBYGS4TZNIZDAMRWFUYDCLJQGE5AAAIAACRAEAYDPB2WQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UF5ZGK5TPMNQXI2LPNYWWY2LTORZS6OBYG5RWIMJUGAWWKNDEG4WTINJRHAWWENZQMYWTGMBVMIZDGNZXHA4DIOAH3BAFA3FHFPDIRRKK4SM3ZDTKQSRCPRCYIA7RFUZYQI3RIGDHIGLAODJ6K2F245DTLIIKXAD35TORFQ7MVRJCIEPH6SC6NGA4HRMK76H5V6GXP66FFNX7MNYC6MYVU7ZLLXYVLXBU" } ``` * `id` : Unique credential identifier. We will use it in the next step to obtain the credential's revocation status, and later to revoke the credential. * `status`: Credential revocation status details. We will use it in the next step to obtain the credential's revocation status. 1. Make a request of the following structure to [create a `did:web`](/docs/api-reference/platform/dids/createDid): ```http title="Request" POST /v1/dids ``` ```json title="Request body" { "method": "web", "options": { "url": "https://learn.vii.au01.mattr.global" // [!code highlight] } } ``` * `url` : Replace with your `tenant_url` provided with your [tenant details](/docs/platform-management/portal#getting-started). *Response* ```json title="Response body" { "did": "did:web:learn.vii.au01.mattr.global" // [!code focus] // Rest of DID document } ``` * `did` : We will use the value of this element to identify the credential's issuer in the next step. If your tenant has a [Custom domain](/docs/platform-management/custom-domain-overview) configured you will need to [setup a redirect](/docs/platform-management/custom-domain-guide#create-redirects-for-required-assets) for this DID document. 2. Make a request of the following structure to [sign a new revocable Semantic CWT credential](/docs/issuance/direct-issuance-api-reference/semantic-cwt-issuance#sign-a-semantic-cwt-credential): ```http title="Request" POST /v2/credentials/compact-semantic/sign ``` ```json title="Request body" { "payload": { "iss": "did:web:learn.vii.au01.mattr.global", // [!code highlight] "vc": { "type": "Course Credential", "credentialSubject": { "name": "Emma Tasma", "code": "HS.278", "certificationName": "Working at Heights", "certificationLevel": "Level 4", "issuerName": "Advanced Safety Training" } } }, "revocable": true, // [!code highlight] "isRevoked": false // [!code highlight] } ``` * `iss` : Replace with the `did` element obtained in the previous step. * `revocable`: We set this to `true` so that this credential can be revoked later in the tutorial. * `isRevoked`: We set this to `false` so that this credential is immediately valid upon issuance. Other use cases might require you to set this to `true` to issue a credential that is revoked by default until it is activated by unrevoking. *Response* ```json title="Response body" { "id": "urn:uuid:78e19496-8521-424b-8315-35fb1ecaf681", // [!code focus] "decoded": { "iss": "did:web:learn.vii.au01.mattr.global", "vc": { "type": ["VerifiableCredential", "Course Credential"], "@context": ["https://www.w3.org/2018/credentials/v1"], "credentialSubject": { "name": "Emma Tasma", "code": "HS.278", "certificationName": "Working at Heights", "certificationLevel": "Level 4", "issuerName": "Advanced Safety Training" } }, "status": { // [!code focus] "index": 0, // [!code focus] "url": "https://learn.vii.au01.mattr.global/v2/credentials/compact-semantic/revocation-lists/1fe00d6c-904f-497e-bbe1-a3cfdc0b8368" // [!code focus] }, "jti": "urn:uuid:78e19496-8521-424b-8315-35fb1ecaf681" }, "encoded": "CSS:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSAOZUYAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQMJ3GHI3IIBRW63TUMV4HJALYEZUHI5DQOM5C6L3XO53S45ZTFZXXEZZPGIYDCOBPMNZGKZDFNZ2GSYLMOMXXMMLEOR4XAZMCORLGK4TJMZUWCYTMMVBXEZLEMVXHI2LBNRYUG33VOJZWKICDOJSWIZLOORUWC3DRMNZGKZDFNZ2GSYLMKN2WE2TFMN2KMZDOMFWWK2SFNVWWCICUMFZW2YLEMNXWIZLGJBJS4MRXHBYWGZLSORUWM2LDMF2GS33OJZQW2ZLSK5XXE23JNZTSAYLUEBEGK2LHNB2HG4TDMVZHI2LGNFRWC5DJN5XEYZLWMVWGOTDFOZSWYIBUNJUXG43VMVZE4YLNMV4BQQLEOZQW4Y3FMQQFGYLGMV2HSICUOJQWS3TJNZTWMZLYOBUXE6LKGIYDENRNGAYS2MBRAQNGSVRXSA5AAAIAACRAEAADPB7GQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UFVZWK3LBNZ2GSYZPOJSXM33DMF2GS33OFVWGS43UOMXTCZTFGAYGINTDFU4TANDGFU2DSN3FFVRGEZJRFVQTGY3GMRRTAYRYGM3DQB6YIBIHRYMUS2CSCQSLQMKTL6Y6ZL3ICWCAUPGIGEOOWODF77V7ZJPVLGAQC2ZUP7MASXIRTIRRPOIIBKNHKZ4LHROPWBPBCYTKA3GXWIRD736HIJNQENTSFUYIPQ77BG4ZPCTXYIY" } ``` * `id` : Unique credential identifier. We will use it in the next step to obtain the credential's revocation status, and later to revoke the credential. * `status`: Credential revocation status details. We will use it in the next step to obtain the credential's revocation status. ### Obtain a credential revocation status [#obtain-a-credential-revocation-status] Now that the credential is issued, different relying parties might be interested in discovering its revocation status. In other words, they want to know whether or not the credential has been revoked by the issuer. MATTR VII supports two ways of achieving this: * Query a protected MATTR VII endpoint to get the revocation status. * Query public MATTR VII endpoints to get the revocation status. Again, this process looks slightly different for different credential formats. **Protected endpoint** Make a request of the following structure to [retrieve the status of an mDoc](/docs/issuance/revocation/api-reference/mdocs-status-update#retrieve-mdocs-status): ```http title="Request" GET /v2/credentials/mobile/{credentialId}/status ``` * `credentialId` : Replace with the [`id`](#prerequisites) of the mDoc you wish to check the status for. You can retrieve the `credentialId` by querying the [Retrieve all users credentials data](/docs/api-reference/platform/users/getUserCredentials) endpoint with the identifier of the user you issued the credential to, and retrieve the `id` from the response. *Response* ```json title="Response body" { "status": "valid" } ``` * `status` : Indicates status for the mDoc. This it the expected value as we have only now issued this mDoc. **Public endpoint** MATTR VII enables relying parties to obtain the status of an mDoc in a privacy preserving manner, as the issuer has no way of knowing what specific mDoc's status the relying party is requesting. This is achieved by retrieving a publicly available [Status list token](/docs/issuance/revocation/overview#status-list-tokens), and then looking up a specific mDoc's status using a reference index that is included in the mDoc itself. These Status lists are based on Draft 14 of the IETF [Token Status List](https://drafts.oauth.net/draft-ietf-oauth-status-list/draft-ietf-oauth-status-list.html) specification. Make a request to the `status.status_list.uri` element from the response obtained when signing the mDoc: ```http title="Request" GET https://learn.vii.au01.mattr.global/v2/credentials/mobile/status-lists/f331c9be-f526-4577-bbac-ae93d6228f7a/token ``` The response will include an encoded Status list token which is a [CBOR Web Token](https://datatracker.ietf.org/doc/html/rfc8392). Relying parties can then decode the list and use the mDoc's `status.status_list.idx` element to locate and check the [status](/docs/issuance/revocation/overview#status-list) of this specific mDoc. **Validating the credential status** You will need two physical devices to perform this validation: * One device with the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) and an mDoc claimed [earlier in this tutorial](#issue-a-revocable-credential). * One device with the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started). Perform the following steps to validate the mDoc status: 1. Open the GO Hold example app. 2. Select the **Wallet** tab. 3. Locate the mDoc claimed earlier in this tutorial. 4. Select the **Share** button. This should display a QR code on screen. 5. Use the GO Verify example app on your second device to scan the displayed QR code. 6. Use the GO Hold example app to confirm sharing the credential. 7. The GO Verify example app should indicate succesful verification, indicating the mDoc is valid. Not for long!!! **Protected endpoint** Make a request of the following structure to [retrieve the revocation status of a CWT credential](/docs/issuance/revocation/api-reference/cwt-status-update#retrieve-cwt-credential-status): ```http title="Request" GET /v2/credentials/compact/{id}/revocation-status ``` * `id` : Replace with the unique identifier of the credential you wish to revoke. This would be the `id` element of the credential you issued in the previous step. It will be available as part of any [CWT credential](/docs/concepts/cwt/data#claims) issued by a MATTR VII tenant. *Response* ```json title="Response body" { "id": "bKcrxojFSuSZvI5qhKInxA", "isRevoked": false // [!code focus] } ``` * `isRevoked` : Indicates revocation status for the credential. Since we issued the credential with `isRevoked` set to `false`, this it the expected value in the response. **Public endpoint** MATTR VII enables relying parties to obtain the revocation status of a credential in a privacy preserving manner, as the issuer has no way of knowing what specific credential's status the relying party is requesting. This is achieved by retrieving a publicly available revocation status list, and then looking up a specific credential's status using a reference index that is included in the credential itself. These revocation lists are compliant with the [W3C Revocation List specification](https://w3c-ccg.github.io/vc-status-rl-2020/). Make a GET request to the `status.url` element from the response obtained when signing the credential ```http title="Request" GET https://learn.vii.au01.mattr.global/v2/credentials/compact/revocation-lists/887cd140-e4d7-4518-b70f-305b23778848 ``` The response will include an encoded revocation list, as per the [W3C Revocation List specification](https://w3c-ccg.github.io/vc-status-rl-2020/). Relying parties can then decode the list and use the credential's `decoded.status.index` element to locate and check the status of this specific credential. Check out our [revocation check sample app](https://github.com/mattrglobal/sample-apps/blob/master/manual-revocation-check/README.md) for a reference implementation. **Validating the credential status** Let's validate this status by trying to verify this credential. Make a request of the following structure to [verify a CWT credential](/docs/api-reference/platform/cwt-credentials-verification/verify-compact-credential): ```http title="Request" POST /v2/credentials/compact/verify ``` ```json title="Request body" { "payload": "CSC:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSALWVQAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQHIAACAACOFBW65LSONSSAQ3SMVSGK3TUNFQWYBA2NFLDPEDENZQW2ZLPIVWW2YJAJJQW4ZJAKRQXG3LBMRRW6ZDFMZEFGLRSG44HCY3FOJ2GSZTJMNQXI2LPNZHGC3LFOJLW64TLNFXGOIDBOQQEQZLJM5UHI43SMNSXE5DJMZUWGYLUNFXW4TDFOZSWYZ2MMV3GK3BAGRVGS43TOVSXETTBNVSXQGCBMR3GC3TDMVSCAU3BMZSXI6JAKRZGC2LONFXGOZTFPBYGS4TZNIZDAMRWFUYDCLJQGE5AAAIAACRAEAYDPB2WQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UF5ZGK5TPMNQXI2LPNYWWY2LTORZS6OBYG5RWIMJUGAWWKNDEG4WTINJRHAWWENZQMYWTGMBVMIZDGNZXHA4DIOAH3BAFA3FHFPDIRRKK4SM3ZDTKQSRCPRCYIA7RFUZYQI3RIGDHIGLAODJ6K2F245DTLIIKXAD35TORFQ7MVRJCIEPH6SC6NGA4HRMK76H5V6GXP66FFNX7MNYC6MYVU7ZLLXYVLXBU", "checkRevocation": true } ``` * `payload` : Replace with the `encoded` element from the response obtained when signing the credential. * `checkRevocation` : This is the property that makes this verification request check for the credential revocation status. *Response* ```json title="Response body" { "verified": true // [!code focus] // Rest of response } ``` * `verified` : As expected, the credential was verified. Not for long!!! **Protected endpoint** Make a request of the following structure to [retrieve the revocation status of a Semantic CWT credential](/docs/issuance/revocation/api-reference/semantic-cwt-status-update#retrieve-semantic-cwt-credential-status): ```http title="Request" GET /v2/credentials/compact-semantic/{id}/revocation-status ``` * `id` : Replace with the unique identifier of the credential you wish to revoke. This would be the `id` element of the credential you issued in the previous step. It will be available as part of any [Semantic CWT credential](/docs/concepts/cwt/data#claims) issued by a MATTR VII tenant. *Response* ```json title="Response body" { "id": "urn:uuid:78e19496-8521-424b-8315-35fb1ecaf681", "isRevoked": false // [!code focus] } ``` * `isRevoked` : Indicates revocation status for the credential. Since we issued the credential with `isRevoked` set to `false`, this it the expected value in the response. **Public endpoint** MATTR VII enables relying parties to obtain the revocation status of a credential in a privacy preserving manner, as the issuer has no way of knowing what specific credential's status the relying party is requesting. This is achieved by retrieving a publicly available revocation status list, and then looking up a specific credential's status using a reference index that is included in the credential itself. These revocation lists are compliant with the [W3C Revocation List specification](https://w3c-ccg.github.io/vc-status-rl-2020/). Make a GET request to the `status.url` element from the response obtained when signing the credential: ```http title="Request" GET https://learn.vii.au01.mattr.global/v2/credentials/compact-semantic/revocation-lists/1fe00d6c-904f-497e-bbe1-a3cfdc0b8368 ``` *Response* The response includes an encoded revocation list, as per the [W3C Revocation List specification](https://w3c-ccg.github.io/vc-status-rl-2020/). Relying parties can then decode the list and use the credential's `decoded.status.index` element to locate and check the status of this specific credential. Check out our [revocation check sample app](https://github.com/mattrglobal/sample-apps/blob/master/manual-revocation-check/README.md) for a reference implementation. **Validating the credential status** Let's validate this status by trying to verify this credential. Make a request of the following structure to [verify a Semantic CWT credential](/docs/api-reference/platform/semantic-cwt-credentials-verification/verifyCompactSemantiCredential): ```http title="Request" POST /v2/credentials/compact-semantic/verify ``` ```json title="Request body" { "payload": "CSS:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSAOZUYAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQMJ3GHI3IIBRW63TUMV4HJALYEZUHI5DQOM5C6L3XO53S45ZTFZXXEZZPGIYDCOBPMNZGKZDFNZ2GSYLMOMXXMMLEOR4XAZMCORLGK4TJMZUWCYTMMVBXEZLEMVXHI2LBNRYUG33VOJZWKICDOJSWIZLOORUWC3DRMNZGKZDFNZ2GSYLMKN2WE2TFMN2KMZDOMFWWK2SFNVWWCICUMFZW2YLEMNXWIZLGJBJS4MRXHBYWGZLSORUWM2LDMF2GS33OJZQW2ZLSK5XXE23JNZTSAYLUEBEGK2LHNB2HG4TDMVZHI2LGNFRWC5DJN5XEYZLWMVWGOTDFOZSWYIBUNJUXG43VMVZE4YLNMV4BQQLEOZQW4Y3FMQQFGYLGMV2HSICUOJQWS3TJNZTWMZLYOBUXE6LKGIYDENRNGAYS2MBRAQNGSVRXSA5AAAIAACRAEAADPB7GQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UFVZWK3LBNZ2GSYZPOJSXM33DMF2GS33OFVWGS43UOMXTCZTFGAYGINTDFU4TANDGFU2DSN3FFVRGEZJRFVQTGY3GMRRTAYRYGM3DQB6YIBIHRYMUS2CSCQSLQMKTL6Y6ZL3ICWCAUPGIGEOOWODF77V7ZJPVLGAQC2ZUP7MASXIRTIRRPOIIBKNHKZ4LHROPWBPBCYTKA3GXWIRD736HIJNQENTSFUYIPQ77BG4ZPCTXYIY", "checkRevocation": true } ``` * `payload` : Replace with the `encoded` element from the response obtained when signing the credential. * `checkRevocation` : This is the property that makes this verification request check for the credential revocation status. *Response* ```json title="Response body" { "verified": true // [!code focus] // Rest of response } ``` * `verified` : As expected, the credential was verified. Not for long!!! ### Revoking an issued credential [#revoking-an-issued-credential] Next we will learn how to revoke issued credentials. The process is very similar for different credential formats but uses different MATTR VII endpoints Make a request of the following structure to [revoke an mDoc](/docs/issuance/revocation/api-reference/mdocs-status-update#update-mdocs-status): ```http title="Request" POST /v2/credentials/mobile/{credentialId}/status ``` * `credentialId` : Replace with the [`id`](#prerequisites) of the mDoc you wish to revoke. ```json title="Request body" { "status": "suspended" } ``` *Response* ```json title="Response body" { "status": "suspended" } ``` * `status` : This is now set to `suspended`, which means the mDoc should not be verified as valid. Make a request of the following structure to [revoke a CWT credential](/docs/issuance/revocation/api-reference/cwt-status-update#update-cwt-credential-status): ```http title="Request" POST /v2/credentials/compact/{id}/revocation-status ``` * `id` : Replace with the `id` of the credential obtained earlier in this tutorial. ```json title="Request body" { "isRevoked": true } ``` *Response* ```json title="Response body" { "id": "bKcrxojFSuSZvI5qhKInxA", "isRevoked": true // [!code focus] } ``` * `isRevoked` : This is now set to `true`, which means the credential will not be verified as valid. Making a similar request with `isRevoked` set to `false` would unrevoke a revoked credential. Make a request of the following structure to [revoke a Semantic CWT credential](/docs/issuance/revocation/api-reference/semantic-cwt-status-update#update-semantic-cwt-credential-status): ```http title="Request" POST /v2/credentials/compact-semantic/{id}/revocation-status ``` * `id` : Replace with the `id` of the credential obtained earlier in this tutorial. ```json title="Request body" { "isRevoked": true } ``` *Response* ```json title="Response body" { "id": "urn:uuid:78e19496-8521-424b-8315-35fb1ecaf681", "isRevoked": true // [!code focus] } ``` * `isRevoked` : This is now set to `true`, which means the credential will not be verified as valid. Making a similar request with `isRevoked` set to `false` would unrevoke a revoked credential. ### Attempting to verify a revoked credential [#attempting-to-verify-a-revoked-credential] The last step will be attempting to verify the revoked credential. This step is also similar across different credential formats, using different endpoints. You will need two physical devices to perform this validation: * One device with the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) and an mDoc claimed [earlier in this tutorial](#issue-a-revocable-credential). * One deivce with the [MATTR GO Verify example app](/docs/verification/go-verify/getting-started). Perform the following steps to validate the mDoc status: 1. Open the GO Hold example app. 2. Select the **Wallet** tab. 3. Locate the mDoc claimed earlier in this tutorial. 4. Select the **Share** button. This should display a QR code on screen. 5. Use the GO Verify example app on your second device to scan the displayed QR code. 6. Use the GO Hold example app to confirm sharing the credential. 7. The GO Verify example app should fail the verification, indicating the credential was revoked by the issuer. Make a request of the following structure to [verify a CWT credential](/docs/api-reference/platform/cwt-credentials-verification/verify-compact-credential): ```http title="Request" POST /v2/credentials/compact/verify ``` ```json title="Request body" { "payload": "CSC:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSALWVQAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQHIAACAACOFBW65LSONSSAQ3SMVSGK3TUNFQWYBA2NFLDPEDENZQW2ZLPIVWW2YJAJJQW4ZJAKRQXG3LBMRRW6ZDFMZEFGLRSG44HCY3FOJ2GSZTJMNQXI2LPNZHGC3LFOJLW64TLNFXGOIDBOQQEQZLJM5UHI43SMNSXE5DJMZUWGYLUNFXW4TDFOZSWYZ2MMV3GK3BAGRVGS43TOVSXETTBNVSXQGCBMR3GC3TDMVSCAU3BMZSXI6JAKRZGC2LONFXGOZTFPBYGS4TZNIZDAMRWFUYDCLJQGE5AAAIAACRAEAYDPB2WQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UF5ZGK5TPMNQXI2LPNYWWY2LTORZS6OBYG5RWIMJUGAWWKNDEG4WTINJRHAWWENZQMYWTGMBVMIZDGNZXHA4DIOAH3BAFA3FHFPDIRRKK4SM3ZDTKQSRCPRCYIA7RFUZYQI3RIGDHIGLAODJ6K2F245DTLIIKXAD35TORFQ7MVRJCIEPH6SC6NGA4HRMK76H5V6GXP66FFNX7MNYC6MYVU7ZLLXYVLXBU", "checkRevocation": true } ``` * `payload` : Replace with the `encoded` element from the response obtained when signing the credential. * `checkRevocation` : This is the property that makes this verification request check for the credential revocation status. *Response* ```json title="Response body" { "verified": false, "reason": { "type": "Revoked", "message": "Credential has been revoked" } // Rest of response } ``` * `verified` : As expected, the credential had failed verification. * `reason` : Details that the credential had failed verification since it has been revoked. Revocation lists are cached for a certain amount of time, so you might need to wait a few minutes before verification would actually fail. Make a request of the following structure to [verify a Semantic CWT credential](/docs/api-reference/platform/semantic-cwt-credentials-verification/verifyCompactSemantiCredential): ```http title="Request" POST /v2/credentials/compact-semantic/verify ``` ```json title="Request body" { "payload": "CSS:/1/2KCE3IQEJB5DCMSMGZITM5QBE2QFSAOZUYAXQI3ENFSDU53FMI5GYZLBOJXC45TJNEXGC5JQGEXG2YLUORZC4Z3MN5RGC3AFDJSZE7YQMJ3GHI3IIBRW63TUMV4HJALYEZUHI5DQOM5C6L3XO53S45ZTFZXXEZZPGIYDCOBPMNZGKZDFNZ2GSYLMOMXXMMLEOR4XAZMCORLGK4TJMZUWCYTMMVBXEZLEMVXHI2LBNRYUG33VOJZWKICDOJSWIZLOORUWC3DRMNZGKZDFNZ2GSYLMKN2WE2TFMN2KMZDOMFWWK2SFNVWWCICUMFZW2YLEMNXWIZLGJBJS4MRXHBYWGZLSORUWM2LDMF2GS33OJZQW2ZLSK5XXE23JNZTSAYLUEBEGK2LHNB2HG4TDMVZHI2LGNFRWC5DJN5XEYZLWMVWGOTDFOZSWYIBUNJUXG43VMVZE4YLNMV4BQQLEOZQW4Y3FMQQFGYLGMV2HSICUOJQWS3TJNZTWMZLYOBUXE6LKGIYDENRNGAYS2MBRAQNGSVRXSA5AAAIAACRAEAADPB7GQ5DUOBZTULZPNRSWC4TOFZ3GS2JOMF2TAMJONVQXI5DSFZTWY33CMFWC6Y3POJSS65RSF5RXEZLEMVXHI2LBNRZS6Y3PNVYGCY3UFVZWK3LBNZ2GSYZPOJSXM33DMF2GS33OFVWGS43UOMXTCZTFGAYGINTDFU4TANDGFU2DSN3FFVRGEZJRFVQTGY3GMRRTAYRYGM3DQB6YIBIHRYMUS2CSCQSLQMKTL6Y6ZL3ICWCAUPGIGEOOWODF77V7ZJPVLGAQC2ZUP7MASXIRTIRRPOIIBKNHKZ4LHROPWBPBCYTKA3GXWIRD736HIJNQENTSFUYIPQ77BG4ZPCTXYIY", "checkRevocation": true } ``` * `payload` : Replace with the `encoded` element from the response obtained when signing the credential. * `checkRevocation` : This is the property that makes this verification request check for the credential revocation status. *Response* ```json title="Response body" { "verified": false, "reason": { "type": "Revoked", "message": "Credential has been revoked" } // Rest of response } ``` * `verified` : As expected, the credential had failed verification. * `reason` : Details that the credential had failed verification since it has been revoked. Revocation lists are cached for a certain amount of time, so you might need to wait a few minutes before verification would actually fail. ## Summary [#summary] In this tutorial you learned how to manage credential revocation, including: 1. How to issue a revocable credential. 2. How to check the revocation status of a credential as an Issuer and as a relying party. 3. How to revoke a credential. You can now integrate these capabilities into your solution to support this important feature. # API Reference URL: /docs/issuance/users/api-reference ## Create a User [#create-a-user] ## Retrieve all Users [#retrieve-all-users] ## Retrieve a User [#retrieve-a-user] ## Update a User [#update-a-user] ## Delete a User [#delete-a-user] ## Search Users [#search-users] ## Retrieve User credentials Data [#retrieve-user-credentials-data] # Making use of Users in MATTR VII Issuance workflows URL: /docs/issuance/users/guide ## Overview [#overview] This guide demonstrates how to leverage the MATTR VII Users concept throughout the credential issuance lifecycle. By properly managing user associations at each phase (before, during, and after issuance) you can maintain relationships between your external user identifiers and credentials, retrieve user-specific claims from external data sources, and trigger downstream business processes based on credential issuance events: * [Before issuing the credential](#before-issuing-the-credential): Ensure the credential is associated with the correct user in MATTR VII. * [During credential issuance](#during-credential-issuance): * Retrieve user-specific claims from external data sources to populate the issued credential. * Create dynamic, user-specific interactions as part of the issuance process. * [After credential issuance](#after-credential-issuance): Trigger downstream business processes based on the user associated with the issued credential. The approach differs depending on whether you're using [Pre-Authorized Code flow](/docs/issuance/pre-authorized-code/overview) or [Authorization Code flow](/docs/issuance/authorization-code/overview), as explained in each section below. ## Before Issuing the Credential [#before-issuing-the-credential] Ensure that the credential will be associated with the correct user in MATTR VII before initiating the issuance process. In [Authorization Code flows](/docs/issuance/authorization-code/overview) user association happens automatically based on the user identifier returned from the authentication provider. In [Pre-authorized Code flows](/docs/issuance/pre-authorized-code/overview) you must explicitly manage user associations before creating the credential offer. This ensures that multiple credentials issued to the same individual are properly linked to a single user record. In Pre-authorized Code flows, if you are issuing credentials to new users who do not yet have a MATTR VII user record, you can omit the user association steps below. When you create the Pre-authorized Code credential offer without specifying a `userId`, MATTR VII will automatically create a new user record when the credential offer is created and return the user ID in the response. You can later update this user record with your external identifier if needed. ### Search for an existing user [#search-for-an-existing-user] Before creating a Pre-authorized Code flow [credential offer](/docs/issuance/credential-offer/overview), use the MATTR VII Search Users API to determine whether a user with your external identifier already exists. This prevents duplicate user records and maintains a consistent relationship between your external user identifiers and MATTR VII users. Make a request of the following structure to search for a user by their external identifier: ```http title="Request" POST /v1/users/search ``` ```json title="Request body" { "claims": { "externalUserId": "945214ad-3635-4aff-b51d-61d69a3c8eee" } } ``` * `externalUserId`: Replace this value with the unique identifier you use in your external system to represent the user. This could be a GUID, UUID, username, or any other stable and unique identifier. If the search returns an empty array, no MATTR VII user currently exists with that external identifier and you can proceed to [create a new user record](#create-user). ```json title="No user found response" { "data": [] } ``` If a matching user is found, the response contains the user object. ```json title="User found response" { "data": [ { "id": "19bf8183-a9dc-41cd-9336-1f5d19f1ae3d", // [!code focus] "claims": { "externalUserId": "945214ad-3635-4aff-b51d-61d69a3c8eee" } } ], "nextCursor": "Y3JlYXRlZEF0PTIwM..." } ``` * `id`: The MATTR VII User ID. Extract and store it as you'll need it for subsequent operations. ### Create User [#create-user] If the search returns no user, create a new user and include your external identifier in its claims. Make a request of the following structure to create a new user: ```http title="Request" POST /v1/users ``` ```json title="Request body" { "claims": { "externalUserId": "945214ad-3635-4aff-b51d-61d69a3c8eee" } } ``` The response includes an `id` field which represents the MATTR VII User ID. Extract and store it as you'll need it for subsequent operations: ```json title="Response" { "id": "19bf8183-a9dc-41cd-9336-1f5d19f1ae3d", // [!code focus] "claims": { "externalUserId": "945214ad-3635-4aff-b51d-61d69a3c8eee" } } ``` ### Create a Pre-authorized Code credential offer [#create-a-pre-authorized-code-credential-offer] When [creating the Pre-authorized Code credential offer](/docs/issuance/credential-offer/guide), include the MATTR VII User ID in the request. This ensures the issued credential is linked to the correct user record. Make a request of the following structure to create a pre-authorized credential offer: ```http title="Request" POST /v1/openid/offers/pre-authorized ``` ```json title="Request body" { "credentials": [ "946c4d4a-289b-4d14-8082-41b6bf749c35" ], "userId": "19bf8183-a9dc-41cd-9336-1f5d19f1ae3d", // [!code focus] "claims": {}, "claimsToPersist": [], "expiresIn": { "minutes": 5, "seconds": 0 } } ``` * `userId`: Set this to the MATTR VII User ID obtained from the previous steps. Alternatively, if you know that this is a new user, you can leave this field out and MATTR VII will create a new user record when the credential offer is created and return the user ID in the response. In the example above, no claims are included in the credential offer creation request. This is typical when using a [Claims Source](/docs/issuance/claims-source/overview) to dynamically retrieve user-specific data during issuance, as detailed in the [next section](#during-credential-issuance) of this guide. ### Authorization Code Flow [#authorization-code-flow] In Authorization Code flows, user association is handled automatically. When a user authenticates, MATTR VII: 1. Receives the authenticated user's subject identifier from the authentication provider. 2. Stores this identifier in the `authenticationProvider.subjectId` field of the user object. 3. Checks for an existing user with a matching `authenticationProvider.subjectId`. 4. Creates a new user if no match is found, or uses the existing user if it is found. 5. Associates the credential with this user. No manual user management is required in this flow. ## During Credential Issuance [#during-credential-issuance] Retrieve user-specific information from external systems during the issuance process when using a [Claims source](/docs/issuance/claims-source/overview) or an [Interaction hook](/docs/issuance/authorization-code/interaction-hook/overview) (Interaction hooks are only available in Authorization Code issuance flows). ### Pre-Authorized Code Flow [#pre-authorized-code-flow] In Pre-Authorized Code flows, include the user's unique external identifier in the credential offer by mapping it into the claims object. When you've configured a [Claims Source](/docs/issuance/claims-source/overview), MATTR VII can use this external identifier to retrieve user-specific data from your external systems and use them in the credential issuance process. **How It Works** 1. A user initiates credential issuance via a Pre-Authorized Code flow. 2. MATTR VII identifies the associated user record using the `userId` provided in the credential offer. 3. MATTR VII extracts the `externalUserId` from the user's claims object. 4. MATTR VII calls the configured Claims Source, passing `externalUserId` as a request parameter. 5. Your Claims Source uses this identifier to look up user-specific data in your systems. 6. The retrieved claims are used in the credential issuance process according to your credential configuration's claim mapping. ### Authorization Code Flow [#authorization-code-flow-1] In Authorization Code flows, your Claims Source or interaction hook custom component must be able to map the `authenticationProvider.subjectId` (the identifier returned from the authentication provider) into the specific user data entry in your external systems. **How It Works** 1. A user initiates credential issuance via an Authorization Code flow and authenticates with the configured [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview). 2. MATTR VII identifies the associated user record using the `authenticationProvider.subjectId`. 3. MATTR VII calls the configured Claims Source, passing the authentication provider's subject identifier. 4. Your Claims Source maps this authentication identifier to your external user identifier. 5. Your Claims Source uses the mapped identifier to look up user-specific data. 6. The retrieved claims are used in the credential issuance process according to your credential configuration's claim mapping. The same workflow applies when a configured interaction hook component requires user-specific data during the issuance process. ### Configuring Your Claims Source Mapping [#configuring-your-claims-source-mapping] In your Claims Source configuration, map the user's external identifier claim to a request parameter that will be sent to your Claims Source server: 1. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 2. Select **Claims sources**. 3. Click the **Create new** button. 4. Complete all required fields as per your Claims source configuration. 5. Paste the required mapping into the *Request parameters* field, for example: ```json title="Request parameters" { "externalUserId": { "mapFrom": "claims.externalUserId" } } ``` 6. Select **Create** to create the Claims source. Make a request of the following structure to [configure a new claims source](/docs/issuance/claims-source/api-reference#configure-a-claims-source): ```http title="Request" POST /v1/claim-sources ``` ```json title="Request body" { "name": "My Claims source", "url": "", "authorization": { "type": "api-key", "value": "supersecretapikey" }, "requestMethod": "GET", "requestParameters": { // [!code focus] "userId": { // [!code focus] "mapFrom": "claims.externalUserId" // [!code focus] } // [!code focus] } } ``` * `requestParameters`: Map the user's `externalUserId` claim in MATTR VII into the `userId` request parameter sent to your Claims Source. MATTR VII includes the mapped parameter in the request to your Claims Source. The Claims Source uses it to fetch and return user-specific claims. MATTR VII then applies these claims to populate the credential according to the credential configuration. For more information on configuring a Claim source, refer to the [overview](/docs/issuance/claims-source/overview) page and the [tutorial](/docs/issuance/claims-source/tutorial). ## After Credential Issuance [#after-credential-issuance] Use [Webhooks](/docs/platform-management/webhooks-overview) to trigger downstream business processes and workflows based on credential issuance events. The webhook payload includes information about the user the credential was issued to, which differs based on the issuance flow: * In Pre-Authorized Code flows, the webhook payload includes the external identifier [you provided](#before-issuing-the-credential) as part of the credential offer. This allows you to directly correlate the issuance event with your external systems. * In Authorization Code flows, the webhook payload includes the `authenticationProvider.subjectId` from the authentication provider. You'll need to map this identifier to the corresponding identifiers in your downstream business systems and processes. ### Retrieving User Credentials metadata [#retrieving-user-credentials-metadata] Another useful operation after credential issuance is retrieving all credentials associated with a specific user. This can be used to audit issued credentials, manage revocations, or analyze user activity. #### Search for a user [#search-for-a-user] Make a request of the following structure to search for a user by their external identifier: ```http title="Request" POST /v1/users/search ``` ```json title="Request body" { "claims": { "externalUserId": "945214ad-3635-4aff-b51d-61d69a3c8eee" } } ``` * `externalUserId`: Replace this value with the unique identifier you use in your external system to represent the user. This could be a GUID, username, email, or any other stable and unique identifier. #### Retrieve User Credentials metadata [#retrieve-user-credentials-metadata] Make a request of the following structure to retrieve all metadata for credentials issued to this user: ```http title="Request" GET /v1/users/{userId}/credentials ``` * `userId`: Set this to the MATTR VII User ID obtained from the previous step. The response returns a `data` array. Each element contains metadata for a credential issued to the user. Fields vary by credential format. See the [API Reference](/docs/api-reference/platform/users/getUserCredentials) for full schema details. You can now use this metadata to trigger any required downstream business processes. ### Implementing Webhook Handlers [#implementing-webhook-handlers] MATTR VII can send webhook notifications for successful credential issuance events. These webhook payloads include the user ID and claims, allowing you to trigger business workflows in your downstream applications. #### Configure a Webhook [#configure-a-webhook] [Configure a webhook](/docs/platform-management/webhooks-guide) for OID4VCI issuance events in your MATTR VII tenant. This will ensure you receive notifications whenever credentials are successfully issued. #### Receive Webhook Notification [#receive-webhook-notification] When a credential is issued, MATTR VII sends a webhook notification containing the user information: ```json title="Webhook Payload Example" { "format": "mso_mdoc", "userId": "19bf8183-a9dc-41cd-9336-1f5d19f1ae3d", // [!code focus] "credentialProfile": "mobile", "credentialConfigurationId": "1d8c7ad7-84ce-4519-8365-7af986e4ee0e", "credentialId": "26b902fc-c1bd-4178-a228-4623b1e3ebee", "userClaims": { "externalUserId": "945214ad-3635-4aff-b51d-61d69a3c8eee" // [!code focus] // Pre-Auth flow }, "credential": "{base64url_encoded_credential}" } ``` * **Pre-Authorized Code flow**: The `userClaims` object contains the external identifier you provided when creating the credential offer. * **Authorization Code flow**: The user information is available through the `userId` field, which you can use to retrieve the full user object (including `authenticationProvider.subjectId`) via the Users API if needed for mapping to your external systems. #### Process Webhook Payload and Trigger Workflows [#process-webhook-payload-and-trigger-workflows] Extract the user information from the webhook payload: * **Pre-Authorized Code flow**: Use `userClaims.externalUserId` to directly identify the user in your systems. * **Authorization Code flow**: Use `userId` to retrieve the user record and map `authenticationProvider.subjectId` to your external systems. You can now use this information to: * Update records in your backend systems * Trigger notifications to users via email, SMS, or push notifications * Update billing systems or usage tracking * Initiate downstream processes such as account provisioning * Log credential issuance events for audit purposes * Trigger approval workflows or compliance checks Refer to the [Webhooks guide](/docs/platform-management/webhooks-guide) to learn more about handling webhook events in MATTR VII. ## Best Practices [#best-practices] 1. **Avoid duplicates**: Use existing user records whenever possible instead of creating new ones. When in doubt, search for the user first. 2. **Store User IDs Securely**: Maintain a secure mapping between your external user identifiers and MATTR VII user IDs in your systems. 3. **Include User ID in Pre-Auth Offers**: Always include the `userId` parameter when creating pre-authorized credential offers to ensure proper user association. 4. **Use Consistent External Identifiers**: Use stable, unique identifiers from your systems (such as GUIDs or user IDs) rather than potentially changing values like email addresses. 5. **Implement Idempotent Webhook Handlers**: Design your webhook handlers to be idempotent, as webhook notifications may be delivered multiple times. 6. **Monitor User Creation Patterns**: Regularly review user creation patterns to ensure your integration is working as expected and not creating duplicate users. 7. **Handle Race Conditions**: In high-concurrency scenarios, implement appropriate locking or conflict resolution strategies when searching for and creating users. # Users URL: /docs/issuance/users/overview ## Overview [#overview] Users in MATTR VII are entities that represent credential holders within the issuance system. They are created and managed automatically as part of OpenID for Verifiable Credential Issuance (OID4VCI) flows, providing a link between credentials issued by MATTR VII and the individuals or entities that hold them. Every credential that is issued through MATTR VII via OID4VCI is associated with a specific user. This association enables issuers to maintain a relationship between the MATTR VII user and external systems, supporting various business workflows and integrations. ## Possible use cases [#possible-use-cases] Associating credentials with users in MATTR VII provides several important capabilities: * **Credential Tracking**: Keeping an organized record regarding who has been issued which credentials. * **External Identity Mapping**: Link MATTR VII users with external user identifiers in your existing systems. * **Claims Retrieval**: Retrieve claims from external data stores ([Claims Sources](/docs/issuance/claims-source/overview)) for specific users during issuance. * **Business Workflow Integration**: Trigger different business processes when credentials are issued to specific users. * **System Updates**: Update external datastores when credentials are issued. * **Notifications and Billing**: Trigger user-specific notifications or billing operations based on credential issuance events. ## User Object Structure [#user-object-structure] A user object in MATTR VII contains the following elements: ```json title="Example User Object" { "id": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba", "claims": { "externalUserId": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba" }, "authenticationProvider": { "providerId": "41458e5a-9092-40b7-9a26-d4eb43c5792f", "url": "https://example-university.au.auth0.com", "subjectId": "example-university-oauth2|123456789" } } ``` * `id`: A unique identifier (UUID) for the user within MATTR VII. This ID is used to reference the user in API operations and is associated with all credentials issued to this user. * `claims`: An object containing custom claims about the user. This object is dynamic and can include any key-value pairs relevant to the user. A common use case is to include an `externalUserId` claim that maps the MATTR VII user to an identifier in an external system. As the contents of the `claims` object are persistent, it is recommended to avoid including sensitive information here when creating users. * `authenticationProvider`: Information about the authentication provider used to verify the user's identity (only applicable for users who have claimed credentials issued via OID4VCI [Authorization code flows](/docs/issuance/authorization-code/overview)): * `providerId`: The unique identifier of the configured authentication provider in MATTR VII. * `url`: The URL of the authentication provider. * `subjectId`: The unique identifier for this user within the authentication provider's system. ## User Creation by Issuance Workflow [#user-creation-by-issuance-workflow] User creation in MATTR VII differs based on the OID4VCI flow being used. Understanding these differences is important for properly managing users in your implementation. ### Authorization Code Flow [#authorization-code-flow] In the Authorization Code flow, the user's identity is only known after they interact with the credential offer and complete authentication. This workflow follows these rules: * You cannot and should not include a `userId` in the credential offer. The system has no way to know the user's identity until they authenticate. * The `userId` is automatically allocated based on the user's unique identifier with the authentication provider (`authenticationProvider.subjectId`): * The user authenticates with the configured authentication provider. * MATTR VII receives the `subjectId` from the authentication provider. * The system checks for an existing user with a matching `authenticationProvider.subjectId`. * If no matching user is found, a new user is created. * The credential is then associated with the existing or newly created user. This ensures that all credentials issued to the same authenticated user are associated with a single user record in MATTR VII. ### Pre-Authorized Code Flow [#pre-authorized-code-flow] In the Pre-Authorized Code flow, the user's identity should be known before they interact with the credential offer. This allows you to control user association explicitly. The `userId` parameter in the API request to create a pre-authorized credential offer is optional, but its usage significantly affects user management. * If you do **not** include a `userId` when creating the credential offer: * There is no way for MATTR VII to associate multiple credentials with the same user. * A new user record is created immediately when the credential offer is created, even if the credential is never claimed. * If you do **include** a `userId` when creating the credential offer: * If a user already exists with this `id`, no new user is created, and the credential is associated with the existing user. * If no user exists with this `id`, a 400 Bad Request error is returned, indicating that the specified user does not exist. For Pre-Authorized Code flow, it is recommended to always include a `userId` in your credential offer when you want to associate multiple credentials with the same user. This gives you full control over user management and enables proper tracking of credentials issued to specific individuals. ## Deleting users [#deleting-users] You can delete a user from your tenant using the [Delete a user](/docs/api-reference/platform/users/deleteUser) API. Deleting a user is irreversible. When a user is deleted, all of their data is removed, and any credential previously issued to them is no longer valid. Because deletion invalidates any credentials previously issued to the user, treat it as a way to revoke a holder's credentials along with removing their data. If you only need to remove specific persisted claims while keeping issued credentials valid, [update the user](/docs/api-reference/platform/users/updateUser) instead of deleting the user. # API Reference URL: /docs/platform-management/analytics/api-reference ## Retrieve Analytic events [#retrieve-analytic-events] # How to query analytic events URL: /docs/platform-management/analytics/guide Monitoring analytics can be performed either via the MATTR Portal or the MATTR VII APIs. 1. Expand the **Platform Management** menu in the navigation panel on the left-hand side. 2. Select **Monitoring**. 3. Use the drop-down list on the upper-left corner to select the tenant you wish to query analytic events for. 4. Query the select tenant analytics database for events using any combination of the following query methods: * Use the *Event ID* or *Request ID* text box to query the database for events that match unique identifiers of specific events or requests you are interested in. * Use the *Events* drop-down list to query the database for events of a specific category. You can also filter for specific types of events within each category. * Use the *Requestor Type* drop-down list to query the database for events generated by specific users, clients or systems. * Use the *Time* drop-down list to query the database for events within a given time frame. You can use any of the existing filters (e.g. Last minute, Last 5 minutes, etc.) or manually set custom `FROM` and `TO` criteria. Any filters you apply will update the list in real-time. Refer to the [Events registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) for an inclusive list of available event categories and types. Alternatively, each endpoint in our [API Reference](/docs/api-reference) indicates what event types can be generated when it is called. Make a request of the following structure to [query MATTR VII analytic events](/docs/platform-management/analytics/api-reference#retrieve-analytic-events): ```http title="Request" GET /v1/events?requestIds=0QdK3jkDSPbOu1Sx9K2lM9 ``` This request queries the tenant for all analytic events that match the provided `requestIds`. You can append any of the available [query parameters](#query-parameters) to your request. *Response* ```http title="Response body" { "data": [ { "id": "f570ff36-9035-439c-8e25-dc0cd67b4b4a", "type": "DID_RETRIEVE_LIST_SUCCESS", "timestamp": "2023-08-20T22:32:59.151Z", "category": "did", "requestId": "0QdK3jkDSPbOu1Sx9K2lM9", "metadata": { "nextCursor": "Y3JlYXRlZEF0PTIwMjMtMDctMzFUMDAlM0EyMSUzQTMwLjUyN1omaWQ9Yzk3NmQ0YmEtNjE2OC00MTkyLWE4YjAtNjE3NzNiZmNhM2M3", "entriesCount": 8 }, "data": null }, { "id": "d7a38f9a-374f-4e4d-9cc0-42149d61540a", "type": "DID_RETRIEVE_LIST_START", "timestamp": "2023-08-20T22:32:59.148Z", "category": "did", "requestId": "0QdK3jkDSPbOu1Sx9K2lM9", "metadata": { "limit": 100 }, "data": null } ], "nextCursor": "dGltZXN0YW1wPTIwMjMtMDgtMjBUMjIlM0EzMiUzQTU5LjE0OFomaWQ9ZDdhMzhmOWEtMzc0Zi00ZTRkLTljYzAtNDIxNDlkNjE1NDBh" } ``` * `data` : This array includes all the events that matched the query parameters. The example response includes two events that were part of a DID retrieval request - one for the start of the request, and one for the successful completion. Each event includes the following parameters: * `id` : Event identifier. * `type` : Event type. * `timestamp` : Event creation date and time. * `category` : Event category. * `requestId` : Request identifier. * `metadata` : Available metadata will vary based on Event type and logging level. * `data` : Available data will vary based on Event type and logging level. * `nextCursor` : This element is used for pagination the response. Refer to our [API Reference](/docs/api-reference#pagination) for more information. ## Query parameters [#query-parameters] * All parameters support comma separated lists. * All query parameters are optional. If no parameters are provided, the most recent 100 events are returned by default. * Use the `limit` and `cursor` parameters in your request to control the response [pagination](/docs/api-reference#pagination). * The following request parameters are available: * `ids` : Query by event IDs. These can be retrieved from the event details. *Example*: `ids=e4c387e7-3e63-40f4-9a38-062aaae9ee50`. * `requestIds` : Query by request IDs. These can be retrieved from the event details. The response will include all the individual events that are part of the queried request. *Example*: `requestIds=0QdK3jkDSPbOu1Sx9K2lM9`. * `categories` : Query by event categories. Every category includes several event `types`. When both the categories and types parameters are provided an `OR` logic is applied, so the response will return all events that match either. Refer to the [Events registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) for an inclusive list. *Example*: `categories=credential_compact`. * `types` : Query by event types. Each event type is part of a `category`. When both the categories and types parameters are provided an `OR` logic is applied, so the response will return all events that match either. Refer to the [Events registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) for an inclusive list. The [API Reference](/docs/api-reference) also details event types that are generated by specific endpoints. *Example*: `categories=CREDENTIAL_COMPACT_SIGN_START`. * `dateFrom` / `dateTo` : Query by event start and/or end date and time, in ISO-8601 format. *Examples*: `dateFrom=2023-06-01T02:45:44.087Z` / `dateTo=2023-06-30T23:59:59.999Z`. * `clientIds` : Query by client IDs. These can be retrieved from the event details. The response will include all the individual events that are part of the queried client. *Example*: `clientIds=1f3a9c2b-7d4e-4c1f-9a6b-8d2e5f1a4b3c`. * `managementUserIds` : Query by management user IDs. These can be retrieved from the event details. The response will include all the individual events that are part of the queried management user. *Example*: `managementUserIds=ea691ed4-90ff-4be2-bd85-f2c74efa72c3`. # Analytics URL: /docs/platform-management/analytics/overview The MATTR VII Analytics APIs allow viewing analytic data from tenant interactions in the form of events. These events form a database that can be seen as a history of all the requests made by a tenant to the platform. The events can be generated with different ranges of data, ranging from no data up to a full response. The Analytics service uses a standard publisher-subscriber architecture. MATTR VII services create and publish events that are pushed to a queue. The Analytics service then consumes these events and stores them in a database. You can [query](/docs/platform-management/analytics/guide) the database for events and retrieve their metadata and data using either direct API requests or the MATTR Portal. The [Events registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) is a comprehensive collection of analytic events generated by the MATTR VII platform. In addition, our [API Reference](/docs/api-reference) indicates what event types can be generated when calling each endpoint. ## Events structure [#events-structure] The structure of MATTR VII analytic events depends on the following: * [**Event type**](#event-types): Different event types generate different event payloads. Refer to the [events registry](#events-registry) for an inclusive list. * [**Event sanitization**](#sanitised-events): MATTR VII analytics can be configured to three different logging levels, and so each event has three possible corresponding payload versions: * Level 1: Metadata only. * Level 2: Non-sensitive data. * Level 3: Full event data. * **Event version**: When new versions of events are introduced (for example as a result of a change to an endpoint response payload structure), different versions of the event could have different payload structures. ### Event types [#event-types] MATTR VII events are usually generated in one of the following scenarios: * `START` events are generated when an operation starts. * `SUCCESS` events are generated following `START` events when the operation succeeds. * `FAIL` events are generated following `START` events when the operation fails. For example, when making a request to [sign a CWT credential](/docs/issuance/direct-issuance-api-reference/cwt-issuance#sign-a-cwt-credential), the following events might be generated: * When a sign operation starts, a `CREDENTIAL_COMPACT_SIGN_START` event is generated. * If the sign operation succeeds, a `CREDENTIAL_COMPACT_SIGN_SUCCESS` event is generated. * If the sign operation fails, a `CREDENTIAL_COMPACT_SIGN_SUCCESS` event is generated. Each event type results in a different events payload structure. #### `START` and `SUCCESS` events [#start-and-success-events] `START` and `SUCCESS` events return payloads of the following structure: ```json title="START and SUCCESS events payload structure" { "id": "string", "type": "literal string", "category": "string", "timestamp": "Long", "version": "string", "tenantId": "string", "requestId": "string", "clientIds": "string", "managementUserIds": "string", "requestIp": "string", "data": "(ServiceDtoInput | ServiceDtoOutput)" } ``` * `id` : Unique event identifier. * `type` : Event type. This might affect the structure of the payload. Refer to the [events registry](#events-registry) for an inclusive list. * `category` : Event category. Refer to the [events registry](#events-registry) for an inclusive list. * `timestamp` : Event start time, in ISO-8601 format. * `version` : Event version. This might affect the structure of the payload. * `tenantId` : Unique identifier of the tenant the event was generated for. * `requestId` : Unique identifier of the request the event is part of. * `clientIds` : Unique identifier of the client who initiated the request. * `managementUserIds` : Unique identifier of the management user who initiated the request. * `requestIp` : IP address from which the request was made. * `data` : The request (for `START` events) or response (for `SUCCESS` events) body. The structure of this Data Transfer Object (DTO) would differ based on the endpoint which generated the event. Refer to the [events registry](#events-registry) to inspect the structure of different event types. ##### `START` and `SUCCESS` events exceptions [#start-and-success-events-exceptions] * Events generated by a `list` operation: * `START` events: The `data` object would contain the `cursor`, `limit` and `id` (when applicable) parameters. Refer to [Pagination](/docs/api-reference#pagination) for more information. * `SUCCESS` events: The `data` object would contain the number of entries in the response. * Events with a large binary DTO: The data object includes either derived data, extracted meaningful data, or a reference to the data source. #### `FAIL` events [#fail-events] `FAIL` events return payloads of the following structure: ```json title="FAIL events payload structure" { "id": "string", "type": "literal string", "category": "string", "timestamp": "Long", "version": "string", "tenantId": "string", "requestId": "string", "clientIds": "string", "managementUserIds": "string", "requestIp": "string", "data": { "error": { "type": "string", "message": "string" } } } ``` * `id` : Unique event identifier. * `type` : Event type. This might affect the structure of the payload. Refer to the [events registry](#events-registry) for an inclusive list. * `category` : Event category. Refer to the [events registry](#events-registry) for an inclusive list. * `timestamp` : Event start time, in ISO-8601 format. * `version` : Event version. This might affect the structure of the payload. * `tenantId` : Unique identifier of the tenant the event was generated for. * `requestId` : Unique identifier of the request the event is part of. * `clientIds` : Unique identifier of the client who initiated the request. * `managementUserIds` : Unique identifier of the management user who initiated the request. * `requestIp` : IP address from which the request was made. * `data` : * `error` : * `type` : Error type as defined by MATTR VII. Note that if the error is due to an exception, type will be `unknown`. * `message` : Error message as defined by MATTR VII. ### Sanitized events [#sanitized-events] When events contain sensitive information, sanitizing them before they are logged or processed helps in removing or anonymising this data, thus preserving privacy. Sanitized versions of MATTR VII analytic events can be stripped of all data, or just sensitive data. Stripped data is configured per end-points depending on the information included in the event body. ## Events registry [#events-registry] The [Events registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) is a comprehensive collection of analytic events generated by the MATTR VII platform. Events are grouped by the service that generates them, which corresponds to the event `category`. Two Events registry are available for the following APIs: * [MATTR VII API](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) * [Management API](https://api-reference-sdk.mattr.global/event-registry-management/latest/index.html) In each registry the following information is available for each event: * Event `type`. * Event structure in the different logging levels: * `Level 1 - Metadata`. * `Level 2 - Non-sensitive data`. * `Level 3 - Full event`. * Event properties: * Properties tagged with `Sanitization Level DATA` are removed from the event structure when sanitization level is set to `Level 1 - Metadata`. * Properties tagged with `Sanitization Level PII` are removed from the event structure when sanitization level is set to `Level 2 - Non-sensitive data`. # Management API URL: /docs/platform-management/management-api/overview ## Overview [#overview] The Management API offers a set of actions to assist system admins to manage the MATTR VII tenants they own. These APIs provide programmatic access to common platform management tasks, including: * Retrieving available environments. * Creating and managing tenants. * Creating and managing clients for accessing tenants. * Managing [Role Based Access Control (RBAC)](/docs/platform-management/access-control) for users and clients interacting with tenants. Similar capabilities are available via the [MATTR Portal](/docs/platform-management/portal). The Management APIs use machine-to-machine authentication through its own credentials, which are different from the client credentials used to access specific MATTR VII tenants. ## Getting started [#getting-started] ### Obtaining a Management API access token [#obtaining-a-management-api-access-token] Before you can make any API requests to manage your MATTR VII tenants, you must complete authentication by obtaining a management API access token from our authentication provider. Use your management API client credentials to make a request of the following structure: ```http title="Request" POST https://auth.manage.au01.mattr.global/oauth/token ``` ```json title="Request body" { "client_id": "F5qae****************************", "client_secret": "Wzc8J**********************************************************", "audience": "https://manage.au01.mattr.global", "grant_type": "client_credentials" } ``` * `client_id` : Replace with the `client_id` value from your management API client credentials. * `client_secret` : Replace with the `client_secret` value from your management API client credentials. * `audience` : Always use `https://manage.au01.mattr.global` as a static value, regardless of your specific management API client credentials. * `grant_type` : Always use `client_credentials` as a static value, regardless of your specific management API client credentials. These are not the same `client_id` and `client_secret` you were provided for accessing other MATTR VII APIs, but rather unique credentials for accessing the Management APIs. If you have not received these credentials or have any questions, please [contact us](mailto:dev-support@mattr.global) before proceeding. *Response* ```json title="Response body" { "access_token": "eyJhb********************************************************************", // [!code focus] "expires_in": 14400, "token_type": "Bearer" } ``` The returned `access_token` must be used as a bearer token for all subsequent requests to any of the protected MATTR VII Management API endpoints, including the next step to create a new tenant. ### Creating a tenant [#creating-a-tenant] Use the returned `access_token` must be used as a bearer token for all requests to the Management API in the next steps to make a request of the following structure to [create a new tenant](/docs/api-reference/management/tenants/createTenant): ```http title="Request" POST https://manage.au01.mattr.global/v1/tenants ``` ```json title="Request body" { "name": "My first tenant", "subdomain": "my-first-tenant", "environmentId": "fa605282-0223-4ae0-831d-af368bc39a55" } ``` * `name` : The name that will be used to identify this tenant. * `subdomain` : The subdomain that will be used to access this tenant. * `environmentId` : The unique identifier of the environment where the new tenant will be created. You can make a request to [retrieve all environments](/docs/api-reference/management/environments/getEnvironments) you have access to and use the `id` value from the response as the `environmentId`. *Response* ```json title="Response body" { "id": "6facbcef-66cd-4a06-89e3-e44a4fc12000", // [!code highlight] "name": "My first tenant", // [!code highlight] "subdomain": "my-first-tenant.vii.au01.mattr.global", "environment": { // [!code highlight] "id": "fa605282-0223-4ae0-831d-af368bc39a55", "name": "Public Australia Sydney", "domain": "vii.a01.mattr.global", "deploymentModel": "public", "authorizationServerDomain": "auth.manage.au01.mattr.global", "region": { "id": "0fd6ce12-a983-41d0-aca8-03e1bb6f6000", "name": "au01", "displayName": "Sydney, Australia" } }, "client": { // [!code highlight] "clientId": "MjQx108p***************FlwJQjy", "clientSecret": "NanfSkVr**********************PfD3zJ" } } ``` * `id` : Globally unique tenant identifier. * `name` : As provided in the request. * `subdomain` : The tenant URL, constructed with the `subdomain` value provided in the request. * `environment` : Indicates data for the environment in which the new tenant was created. * `client` : Indicates the `clientId` and `clientSecret` for the default client created for this tenant. This client is assigned an [Admin](/docs/platform-management/access-control#tenant-admin-permissions) role by default, meaning it has access to all endpoints in the tenant. Once the tenant is created you can interact with it as detailed in the Portal [getting started](/docs/platform-management/portal#interacting-with-the-tenant) guide. Inviting users to a tenant is only supported for Portal users that are members of the tenant. The machine-to-machine (M2M) client used to access the Management API cannot invite users. Calling the [invite a tenant member](/docs/api-reference/management/members/inviteTenantMember) endpoint with an M2M client token returns a `404 Resource Not Found` response, even when the client holds an `admin` role. A tenant created with an M2M client has no Portal members, so no one can be invited to it until a Portal user is a member. To manage tenant membership, create the tenant from the [MATTR Portal](/docs/platform-management/portal#inviting-users) instead. The Portal user that creates the tenant becomes a member automatically and can then invite additional users, either from the Portal UI or by calling the invitation endpoint with that Portal user's access token. # How to create verifier certificates URL: /docs/verification/certificates/guide Verifiers request credentials for verification by sending verification requests to credential holders. During this process, holders can confirm the verifier’s identity using a [chain of trust](/docs/concepts/chain-of-trust), which links the verification request to the verifier through a series of certificates. MATTR VII supports both managed and unmanaged (external) verifier certificates, giving verifiers flexibility in how they manage their certificate infrastructure. * With *managed* verifier certificates, MATTR VII provisions and maintains the Verifier root CA and Verification Request Signer Certificates (VRSCs) for you. If no managed Verifier root CA exists when you create the first [verifier application](/docs/verification/remote-verification-api-reference/verifier-applications) in a tenant, one is created automatically. The first time that application creates a verification request, MATTR VII also creates a Verification Request Signer (and its certificate) and uses it to sign the request. * With *unmanaged* verifier certificates, the verifier supplies and maintains the full certificate chain: generating and protecting the Verifier root CA, issuing and rotating Verification Request Signer Certificates (VRSCs), and uploading the root CA and each VRSC to MATTR VII. See the [verifier chain of trust](/docs/concepts/chain-of-trust#external-certificates) for details. The following guide describes how to use MATTR VII to configure a verification solution using *unmanaged* (external) verifier certificates. ### Generate a self-signed root certificate (Verifier root CA) [#generate-a-self-signed-root-certificate-verifier-root-ca] Use your preferred cryptographic library or tool to generate a self-signed root certificate (Verifier root CA certificate). This certificate will later be used to sign the Verification Request Signer Certificates (VRSCs). Ensure it meets the requirements specified in the [certificate requirements](/docs/verification/certificates/overview#certificate-requirements) section. When using unmanaged (external) certificates, the DTS provider assumes full responsibility for the secure management of the uploaded root certificates and all subordinate certificates. This includes ensuring the protection, proper issuance, and timely revocation of certificates under the uploaded root, as MATTR VII does not manage or monitor these certificates on the issuer's behalf. ### Register the external Verifier root CA certificate with MATTR VII [#register-the-external-verifier-root-ca-certificate-with-mattr-vii] Make a request of the following structure to [create an unmanaged Verifier root CA](/docs/verification/certificates/api-reference/verifier-root-ca-certificates#create-a-verifier-root-ca-certificate): ```http filename:"Request" POST /v2/presentations/certificates/ca ``` ```json filename:"Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0zMzA5\r\nMDgyMzM0MjJaMCIxIDAJBgNVBAYTAk5aMBMGA1UEAxMMRXhhbXBsZSBJQUNBMFkw\r\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBbK7JKKFMWuu8kHQK2qaML+MQ0Ykk3Qg\r\n/p3TC6lQKvYJozPSpLXbJQIzMPq9u/dG+j4vq1iX/G/jFIwfiEiKEqOB0TCBzjAS\r\nBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIABjAdBgNVHQ4EFgQU9zTh\r\nKsqFxAgRJDDGW1au+ewJK6owHgYDVR0SBBcwFYYTaHR0cHM6Ly9leGFtcGxlLmNv\r\nbTBpBgNVHR8EYjBgMF6gXKBahlhodHRwczovL2V4YW1wbGUuY29tL3YyL2NyZWRl\r\nbnRpYWxzL21vYmlsZS9pYWNhcy8yZTg5YzE1Ni0zMWQ1LTQ3ODMtYmQ1OS05MDU1\r\nYjVmOGU3ZDIvY3JsMAoGCCqGSM49BAMCA0gAMEUCIQDD+eU8iOsYYC0v41L94fhF\r\nZ0brPo4gx2aRxrhE3NLFpwIgIgHCPBXJ+JICJg3K7dEsr153So4SEZzAA1rRn4eF\r\nvkM=\r\n-----END CERTIFICATE-----\r\n" } ``` * `certificatePem` : This required parameter contains the PEM-encoded Verifier root CA certificate. The certificate must meet the following requirements: * Valid * Not expired * Compliant with MATTR VII's [certificate requirements](/docs/verification/certificates/overview#certificate-requirements). The response will include an `id` property, which is a unique identifier for the unmanaged Verifier root CA. This identifier will be used in subsequent operations to reference this unmanaged Verifier root CA. ### Create a Verification Request Signer [#create-a-verification-request-signer] Make a request of the following structure to [create a Verification Request Signer](/docs/verification/certificates/api-reference/verification-request-signers#create-a-verification-request-signer) that references the unmanaged Verifier root CA: ```http filename:"Request" POST /v2/presentations/certificates/verifier-signers ``` ```json filename:"Request body" { "caId": "080c670a-2e90-4023-b79f-b706e55e9bc6" } ``` * `caId` : Replace with the `id` value obtained when you created the unmanaged Verifier root CA in the previous step. Attempts to provide a managed Verifier root CA identifier for manual Verification Request Signer creation will result in an error. The response will include two properties which you will use later in this guide: * `id` : The unique identifier for the Verification Request Signer. This identifier will be used in subsequent operations to reference this Verification Request Signer. * `csrPem` : The X.509 Certificate Signing Request (CSR) in PEM format. You will use this CSR to generate a valid Verification Request Signer Certificate (VSC) in the next step. ### Generate and sign the Verification Request Signer Certificate (VRSC) [#generate-and-sign-the-verification-request-signer-certificate-vrsc] Use your preferred cryptographic library or tool to generate and sign a Verification Request Signer Certificate (VRSC) using the CSR provided in the response from the previous step. Refer to the [certificate requirements](/docs/verification/certificates/overview#certificate-requirements) section in the external Verifier certificates documentation for details on how to structure a valid VRSC. ### Associate the VRSC with the Verification Request Signer [#associate-the-vrsc-with-the-verification-request-signer] Make a request of the following structure to [update the Verification Request Signer](/docs/verification/certificates/api-reference/verification-request-signers#update-a-verification-request-signer) to activate and associate it with the generated VRSC: ```http filename:"Request" PUT /v2/presentations/certificates/verifier-signers/{verifierSignerId} ``` * `verifierSignerId` : Replace with the `id` value obtained when you created the Verification Request Signer in the previous step. ```json filename:"Request body" { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICbzCCAhSgAwIBAgIKfS7sskyJEh+DOzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0yNDA5\r\nMTAyMzM0MjJaMDExLzAJBgNVBAYTAk5aMCIGA1UEAxMbZXhhbXBsZS5jb20gRG9j\r\ndW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7fa+jv9zCtHQ\r\nmKn7o1dS6lBHD5thlhPqjlx7qEfqy8Im9AcQJDal2sr/fUxhHwf/G4ublS7AL04U\r\n73dzr/ozxaOCASEwggEdMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLdNNPTmPxt0\r\nLqvlZnV/QL86MXOxMB8GA1UdIwQYMBaAFPc04SrKhcQIESQwxltWrvnsCSuqMA4G\r\nA1UdDwEB/wQEAwIAgDAeBgNVHREEFzAVhhNodHRwczovL2V4YW1wbGUuY29tMB4G\r\nA1UdEgQXMBWGE2h0dHBzOi8vZXhhbXBsZS5jb20waQYDVR0fBGIwYDBeoFygWoZY\r\naHR0cHM6Ly9leGFtcGxlLmNvbS92Mi9jcmVkZW50aWFscy9tb2JpbGUvaWFjYXMv\r\nMmU4OWMxNTYtMzFkNS00NzgzLWJkNTktOTA1NWI1ZjhlN2QyL2NybDASBgNVHSUE\r\nCzAJBgcogYxdBQECMAoGCCqGSM49BAMCA0kAMEYCIQCfgn6+QoNfDVelJANl+Jp9\r\ncq7X9paZylfnI6UGr1FM6gIhAIzhiyclDa8+/ZSRfu7KfgGrNRaJ8YQ6vevskJls\r\nIavC\r\n-----END CERTIFICATE-----\r\n" } ``` * `active` : This required boolean indicates whether the Verification Request Signer is active or not. Can only be set to `true` when a `certificatePem` is provided. Only active Verification Request Signers can be used to sign verification requests. * `certificatePem` : This required parameter contains the PEM-encoded VRSC created in the previous step. ### Activate the Verifier root CA [#activate-the-verifier-root-ca] Make a request of the following structure to [update the unmanaged Verifier root CA](/docs/verification/certificates/api-reference/verifier-root-ca-certificates#update-a-verifier-root-ca-certificate) and activate it: ```http filename:"Request" PUT /v2/presentations/certificates/ca/{certificateId} ``` * `certificateId` : Replace with the `id` value obtained when you registered the unmanaged Verifier root CA. ```json filename:"Request body" { "active": true } ``` ### Create a Verification Request [#create-a-verification-request] Once the Verifier root CA and Verification Request Signer are activated, they can be used to sign verification requests. MATTR VII will automatically select a valid and active Verification Request Signer when attempting to [create a remote verification request](/docs/verification/remote-overview#verification-requests). If there is no valid and active Verification Request Signer, MATTR VII will return an error stating that no valid Verification Request Signer is available for signing. Unlike the managed flow, MATTR VII does not automatically create new Verification Request Signers in the unmanaged flow, and the verifier is responsible for manually creating and uploading them as needed. # Overview URL: /docs/verification/certificates/overview ## Chain of trust [#chain-of-trust] When verifiers request digital credentials, holders need a way to confirm the verifier’s identity and decide whether to trust them with their information. This is accomplished using a [chain of trust](/docs/concepts/chain-of-trust), a hierarchy of certificates that proves the verifier’s authenticity. The following diagram depicts how MATTR implements the chain of trust model when signing verification requests: Chain of trust ## Certificate requirements [#certificate-requirements] The following lists depicts the requirements for external certificates used in MATTR VII. Some of the requirements are common across all certificates, while others are specific to the type of certificate (Verifier root CA, VRSC). * When using **managed verifier certificates**, MATTR VII **automatically** ensures that all certificates meet these requirements. * When using **unmanaged verifier certificates**, it is the responsibility of the **verifier** to ensure compliance. ### Common certificate requirements [#common-certificate-requirements] * Certificate format & basic attributes: * PEM format must contain a valid X.509 certificate. * Version must be v3. * `Issuer` field must be present and valid. * Issuer Alternative Name must be present and contain a valid email address or URI. * Serial Number: * Must be present. * Must contain 1-20 digits (**Best practice**: Use a positive, non-sequential value). * Subject attributes: * Subject field must be present: * Country (C): must be present and be a valid [ISO 3166-1 alpha-2 code](https://www.iso.org/glossary-for-iso-3166.html). * Common Name (CN): must be present and non-empty. * Public Key requirements: * Subject Public Key must be present. * Extensions: * Certificate must include extensions. * Duplicate extensions are not allowed (No more than one extension with the same `extnID`). * Mandatory extensions: * Subject Key Identifier: Must be present and non-empty. * Key Usage: * Must be present. * Must match the intended use of the certificate (e.g. Verifier root CA, VRSC). * Validity period: * `NotAfter` must be after `NotBefore`. * `NotAfter` cannot be in the past (expired certificates are invalid). * Future dated certificates are valid (e.g. `notBefore` can be in the future). * Must be within allowed limits for certificate type: * VRSC: Maximum 1187 days from issuance. * Certificate Revocation List (CRL): * If a CRL is provided, it must be valid and signed by the verifier root CA. * The CRL must be accessible via a valid URI. ### Verifier root CA specific requirements [#verifier-root-ca-specific-requirements] * Must include the `keyCertSign` and `cRLSign` key usages. * Basic constraints must be present and `CA` must be set to `TRUE`. * Issuer Alternative Name must be present and contain a valid email address or URI. * Signature must be self-signed and verifiable. * Public key must use one of the supported public key algorithms and curves as defined in ISO/IEC 18013-5:2021 B.3: * ECDSA curves: `P-256`, `P-384`, `P-521`, `brainpoolP256r1`, `brainpoolP320r1`, `brainpoolP384r1`, `brainpoolP512r1` * EdDSA key types: `Ed25519`, `Ed448` ### VRSC specific requirements [#vrsc-specific-requirements] * Must be signed by a valid Verifier root CA. * Common name must differ from parent/root Verifier root CA. * `Issuer` field must be present and must match the exact binary value of the Verifier root CA certificate subject. * Must include the `digitalSignature` key usage exclusively. * Must include the `Subject Alternative Name` extension with a value that matches the requirement in the CSR. * Extended key usage must be present and include the correct OID: * `1.0.18013.5.1.6`. * Signature must be verifiable against the Verifier root CA. * Authority Key Identifier must be present and match the Verifier root CA's Subject Key Identifier. * Must not exceed parent Verifier root CA's validity period (i.e. `notBefore` and `notAfter` must be within the Verifier root CA's validity period). * Public key must match the CSR provided during Verification Request Signer creation. ### Example certificates [#example-certificates] See an example of valid certificates parsed using the MATTR Labs [X.509 certificate decoder](https://tools.mattrlabs.com/pem): * [Verifier root CA](https://tools.mattrlabs.com/pem?cert=MIICTDCCAfKgAwIBAgIKeKcjIBGvXfS%252FsjAKBggqhkjOPQQDAjAwMQswCQYDVQQGEwJVUzEhMB8GA1UEAwwYRXhhbXBsZSBWZXJpZmllciByb290IENBMB4XDTI1MDgyNzIxMzMxNFoXDTMwMDgyNjIxMzMxNFowMDELMAkGA1UEBhMCVVMxITAfBgNVBAMMGEV4YW1wbGUgVmVyaWZpZXIgcm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABNJHM5ZE%252BfpVn7b9WwjVBiOiZq9eNXq1JkNj%252F6ZLe%252B2GkaRY%252FWE2Xbg7yx%252B%252Bh3QEdX3sGKzGO7dygQALBe%252F4qEyjgfMwgfAwHQYDVR0OBBYEFK8ogqdUH2vZlC1yNf619a8fnx8KMA4GA1UdDwEB%252FwQEAwIBBjASBgNVHRMBAf8ECDAGAQH%252FAgEAMHsGA1UdHwR0MHIwcKBuoGyGamh0dHBzOi8vbGVhcm4udmlpLmF1MDEubWF0dHIuZ2xvYmFsL3YyL3ByZXNlbnRhdGlvbnMvY2VydGlmaWNhdGVzL2Q3YzE3ODI4LThkMTgtNDYyZS1iNDk3LWNjNjI2NWM4ZmQxYi9jcmwwLgYDVR0SBCcwJYYjaHR0cHM6Ly9sZWFybi52aWkuYXUwMS5tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSAAwRQIhAOqD0DF3rohBitl5jAj6x1164uGGj6yAhF%252FeE4aJeGc%252BAiAgaUYHzobzaPEWd%252BjZOh%252FAq8WgVJ%252B8sLx9WdJDs9%252FshQ%253D%253D) * [VRSC](https://tools.mattrlabs.com/pem?cert=MIIC4zCCAomgAwIBAgIKY6wskKU1HpGZwTAKBggqhkjOPQQDAjAwMQswCQYDVQQGEwJVUzEhMB8GA1UEAwwYRXhhbXBsZSBWZXJpZmllciByb290IENBMB4XDTI1MDgyNzIxMzMxNVoXDTI2MDIyNTIxMzMxNVowSTELMAkGA1UEBhMCVVMxOjA4BgNVBAMMMWxlYXJuLnZpaS5hdTAxLm1hdHRyLmdsb2JhbCBSZWFkZXIgQXV0aGVudGljYXRpb24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATm3hQ84rKlvympzg%252FSEJ9jX2vP36GDLoHZLRLtaCOrOZvfS4u99ZJlDAzyFkjYVRTSdT0LmeCpu5VhbmY440X5o4IBcDCCAWwwHQYDVR0OBBYEFAWILE2UoP0%252Frk5J2RtgbXOSiH3mMA4GA1UdDwEB%252FwQEAwIHgDAuBgNVHRIEJzAlhiNodHRwczovL2xlYXJuLnZpaS5hdTAxLm1hdHRyLmdsb2JhbDBLBgNVHREERDBChiNodHRwczovL2xlYXJuLnZpaS5hdTAxLm1hdHRyLmdsb2JhbIIbbGVhcm4udmlpLmF1MDEubWF0dHIuZ2xvYmFsMHsGA1UdHwR0MHIwcKBuoGyGamh0dHBzOi8vbGVhcm4udmlpLmF1MDEubWF0dHIuZ2xvYmFsL3YyL3ByZXNlbnRhdGlvbnMvY2VydGlmaWNhdGVzL2Q3YzE3ODI4LThkMTgtNDYyZS1iNDk3LWNjNjI2NWM4ZmQxYi9jcmwwHwYDVR0jBBgwFoAUryiCp1Qfa9mULXI1%252FrX1rx%252BfHwowIAYDVR0lAQH%252FBBYwFAYJKwYBBAGD4GoCBgcogYxdBQEGMAoGCCqGSM49BAMCA0gAMEUCIQCiV7ny8MGw0S8QwkBr28hmXmsXSIbrfNimKCiDMXtAyQIgBZAy3WNqme4zBND4P7z0mFBwxC4CeJ77zaVlQrHnUZI%253D) # MATTR GO Verify URL: /docs/verification/go-verify/getting-started ## Overview [#overview] **MATTR GO Verify** is a ready-to-use, white-label mobile application for in-person verification of digital credentials. It enables organisations to verify credentials confidently and securely, and can be fully customised with your own branding, colours, and typography for direct distribution to relying parties and/or end users. The **MATTR GO Verify example app** is a publicly available reference implementation that demonstrates the capabilities of a white-labelled MATTR GO Verify app. It can be used alongside the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) to explore and test in-person credential presentation and verification workflows. ## Getting started [#getting-started] ### Download [#download] Download the MATTR GO Verify example app to your mobile device from: * The [App Store](https://apps.apple.com/app/mattr-go-verify/id6670461328) for iOS devices. * [Google Play](https://play.google.com/store/apps/details?id=global.mattr.mobile.verifier) for Android devices. ### Verify mDocs [#verify-mdocs] 1. Use a different device to download the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started#download-the-app). 2. Use the GO Hold example app to [claim an mDoc](/docs/holding/go-hold/getting-started#claim-a-credential). 3. In the GO Hold example app select the **Share** button and then select **Share Credential**. 4. Select the *Connection QR* tab. This should display a QR code on the screen. 5. Open the GO Verify app. 6. Select the **Verify** button. 7. Scan the QR code displayed in the GO Hold example app. You may need to allow the GO Verify app to access your camera. 8. Follow the on-screen instructions to complete the [proximity verification](/docs/verification/in-person-overview) workflow. ### Dive deeper [#dive-deeper] Sign up for a [MATTR VII tenant](/docs/resources/get-started), issue different credentials into your [GO Hold example app](/docs/holding/go-hold/getting-started) and then use the GO Verify example up to verify them: * [OID4VCI Authorization Code tutorial](/docs/issuance/authorization-code/tutorial). * [OID4VCI Pre-authorized Code tutorial](/docs/issuance/pre-authorized-code/tutorial). # Libraries in use URL: /docs/verification/go-verify/libraries The following lists all notices for third party software that may be used in some way by MATTR GO Verify and other development tools. We value the contributions by open source developers and thank them. # System requirements URL: /docs/verification/go-verify/system-requirements Description: Minimum requirements and supported devices for the MATTR GO Verify example app. ## Operating systems [#operating-systems] The following operating system versions represent the minimum supported platforms for MATTR GO Verify. * iOS 15 or higher * Android 7 (API level 24) or higher MATTR validates functionality using currently supported operating system releases provided by Apple and Google. Compatibility with manufacturer-specific Android variants may vary depending on device implementation. For optimal security and performance, devices should run the latest available operating system updates. ## Required device permissions and configuration [#required-device-permissions-and-configuration] Certain device permissions and user settings are required for the application to function correctly, including but not limited to the following: * Enabling camera access: The application requires access to the device camera for in-person verification flows. If camera access is disabled, these flows will not function. * Enabling biometrics: Some application security features rely on biometric authentication. If biometrics are not enabled on the device, these features will not be available. * Device text size and font scaling: Extreme text size or font scaling settings may affect layout, readability, or visibility of on-screen controls. Users should configure these settings to meet their accessibility needs while still allowing the app interface to display correctly. * Connectivity and settings: Some features require access to the public internet. Users must ensure a stable connection is available. ## Tested devices [#tested-devices] App functionality relies on several device capabilities, including: * Camera access for in-person verification flows * Secure storage and hardware-backed key protection * Biometric authentication * Reliable network connectivity Variations in hardware, operating system customization, and manufacturer implementations can affect these capabilities. For this reason, MATTR verifies functionality only on the devices listed below. Devices not listed here are not verified by MATTR and functionality or performance on those devices is not guaranteed. ### iOS [#ios] * iPhone 15 Pro * iPhone 15 * iPhone 14 Plus * iPhone 14 * iPhone 12 * iPhone 11 Pro * iPhone XR * iPhone XS * iPhone SE ### Android [#android] #### Samsung [#samsung] * Samsung Galaxy S24 * Samsung Galaxy A55 * Samsung Galaxy S23 FE * Samsung Galaxy S22 * Samsung Galaxy S20 FE 5G * Samsung Galaxy Note20 5G #### Google [#google] * Google Pixel 7 Pro * Google Pixel 7 * Google Pixel 6 Pro MATTR may update the list of verified devices and supported operating system versions as new devices and platform updates are released. Customers should ensure their environments remain aligned with the current requirements. # Handling verification results URL: /docs/verification/in-person-guides/handling-verification-results Description: Learn how to interpret mDoc verification results returned by the mDocs Verifier SDK in proximity presentation flows. When a holder responds to an in-person (proximity) verification request, the mDocs Verifier SDK returns structured data containing the verification outcome. This guide explains the response structure and how to interpret it. ## What gets verified [#what-gets-verified] Before working through the response structure, it is worth being clear about what is actually being verified. The credential itself, the full mDoc as issued and stored in the holder's wallet, never leaves the wallet and is never shared with a verifier. What the holder shares is a *credential presentation*: a subset of the credential's data, accompanied by the issuer's signature over the released items and a device authentication that proves the holder's device authorized this specific presentation. This model is grounded in [ISO/IEC 18013-5](https://www.iso.org/standard/69084.html), which defines the mDL/mDoc data model and the proximity presentation flow used here. The standard treats what the holder shares as a presentation derived from the credential rather than the credential itself, and the presentation carries the same cryptographic proof and integrity guarantees as the credential it is drawn from. When you verify a response, you are verifying the credential presentation. The cryptographic trust checks (issuer signature, issuer trust, validity, revocation, device key binding) apply to that presented slice, not to the credential as a whole on the holder's device. The MATTR VII `verificationResult.verified` flag reflects whether this presented credential passed those checks. The rest of this guide uses "credential" as shorthand for the presented credential when the context makes clear we are talking about what was returned in the response. ## How results are delivered [#how-results-are-delivered] In a proximity presentation flow, the holder's device communicates directly with your verifier application. After the holder responds to the presentation request, the SDK returns a `MobileCredentialResponse` object. Your application receives the result inline and is responsible for parsing and acting on it according to your business logic. ## Understanding the response [#understanding-the-response] A `MobileCredentialResponse` contains two optional fields: * **`credentials`**: An array of `MobileCredentialPresentation` objects representing credentials that were successfully returned by the holder. * **`credentialErrors`**: An array of `CredentialError` objects representing credentials that couldn't be returned. When a holder responds to a verification request, several outcomes are possible, often in combination. Each one is described along two independent axes: whether the requested credential itself verified, and whether the requested claims within it were provided. 1. **Requested credential not presented**: The holder doesn't have the requested credential, so there is nothing to verify. The credential appears under `credentialErrors` with an error code of `notReturned`, and no `verificationResult` is produced. 2. **Presented credential not verified**: A credential was provided but the credential-level trust checks failed. The `credentials` array contains the credential with `verified: false` and a `reason.type` explaining why. Even if claims were returned, they should not be relied on while the credential itself did not verify. 3. **Presented credential verified, all claims provided**: The credential-level checks pass (`verified: true` with no `reason`) and every requested claim is returned (no `claimErrors`). Both layers are clean. 4. **Presented credential verified, some claims missing**: The credential-level checks pass (`verified: true`), but one or more requested claims were not returned and appear under `claimErrors`. The credential is trustworthy; the data payload is incomplete. How to handle the missing data is up to the relying party, depending on its use case. Options include failing the flow, falling back to collecting the required data through another channel, or proceeding if the missing claim is not essential. ## Detailed result structure [#detailed-result-structure] ### Credential-level information [#credential-level-information] Each `MobileCredentialPresentation` in the `credentials` array contains: * **`docType`**: The credential type (e.g., `org.iso.18013.5.1.mDL` for a mobile driver's license). * **`claims`**: Verified claims organized by namespace. * **`claimErrors`**: Errors for individual claims that couldn't be verified or were not returned. * **`validityInfo`**: Credential validity period timestamps (`signed`, `validFrom`, `validUntil`, `expectedUpdate`). * **`verificationResult`**: Verification status containing: * **`verified`**: Boolean indicating if verification succeeded. This is a high-level result; individual claim errors may still exist. * **`reason`** (optional): Object explaining verification failures when `verified` is `false`. Contains a `type` value from the following: * `DeviceKeyInvalid`: Device key is not valid. This can occur if the credential was not properly bound to the device or if the device's secure element is compromised. * `InvalidSignerCertificate`: Invalid signer certificate. This can occur if the credential's signing certificate is not valid or has been tampered with. * `IssuerNotTrusted`: Credential was issued by a certificate that is not trusted by the verifier. This can occur if the issuer's certificate is not included in the verifier's trusted issuer list. * `MobileCredentialExpired`: Credential expired. This can occur if the current date is after the credential's `validUntil` date. * `MobileCredentialInvalid`: Credential is not valid. This can occur for various reasons such as failing signature verification, containing invalid data, or not conforming to expected formats. * `MobileCredentialNotYetValid`: Credential not yet valid. This can occur if the current date is before the credential's `validFrom` date. * `StatusRevoked`: Credential has been revoked. This can occur if the issuer has revoked the credential after it was issued, which is typically checked through a revocation mechanism provided by the issuer. * `StatusUnknown`: Credential status could not be determined. This can occur if the verifier is unable to check the revocation status of the credential due to network issues or if the issuer does not provide a revocation mechanism. * `TrustedIssuerCertificateExpired`: Trusted issuer certificate expired. This can occur if the certificate of the trusted issuer has passed its expiration date, which may affect the trustworthiness of credentials issued by that issuer. * `TrustedIssuerCertificateNotYetValid`: Trusted issuer certificate not yet valid. This can occur if the certificate of the trusted issuer is not yet valid (i.e., the current date is before the certificate's `validFrom` date), which may affect the trustworthiness of credentials issued by that issuer. * `UnsupportedCurve`: Credential object contains an unsupported curve. This can occur if the credential uses cryptographic curves that are not supported by the verifier's cryptographic library, which may prevent successful verification of the credential's signatures. * **`issuerInfo`** (optional): Issuer details including `commonName` and `trustedIssuerId`. * **`branding`** (optional): Visual information for displaying the credential (name, description, colors, logos). You can use this to create a rich user interface when showing credential details in your application. The examples on this page show the serialized response, where the failure object is the `reason` field. In the native iOS (v6.0.0+) and Android (v7.0.0+) Verifier SDKs, this is accessed in code via the `failureType` property (it still serializes as `reason`). In the React Native SDK it is accessed as `reason`. ### Claim-level information [#claim-level-information] Claims are organized by namespace within the `claims` object. For example, for an mDL, claims appear under the `org.iso.18013.5.1` namespace: ```json title="Example claims structure for a verified mDL credential" "claims": { "org.iso.18013.5.1": { "family_name": { "value": "Smith" }, "given_name": { "value": "Jane" }, "birth_date": { "value": "1990-05-15" }, "address": { "value": "123 Main Street, Springfield" } } } ``` If specific claims couldn't be verified or weren't provided, they appear in the `claimErrors` object with the same namespace structure and an error code of `notReturned`: ```json title="Example claim errors for a credential with a missing claim" "claimErrors": { "org.iso.18013.5.1": { "portrait": "notReturned" } } ``` ### Credential errors [#credential-errors] When a requested credential wasn't provided by the holder, it appears in the `credentialErrors` array with a `docType` and an `errorCode` of `notReturned`: ```json title="Example credential errors for a missing credential" "credentialErrors": [ { "docType": "org.iso.18013.5.1.mDL", "errorCode": "notReturned" } ] ``` ## Understanding the `verified` flag [#understanding-the-verified-flag] With the structure in mind, it is worth being explicit about what `verificationResult.verified` does and does not tell you, because it is the single most important field for deciding whether to trust a presentation. `verified` is the pass/fail flag for the presented credential. It applies to the slice of credential data the holder released in this session, together with its associated issuer signature and device authentication, not to the credential as a whole as stored on the holder's device. When `verified: true`, what the holder presented has passed MATTR VII's cryptographic and trust checks: the issuer signature on the released items is intact, the credential was issued by a trusted issuer, it is not expired or revoked, and the device key binding holds. In plain terms, the presented mDoc is genuine, has not been tampered with, and is currently valid. What `verified: true` does not mean is that every claim you requested came back. `verified` is a high-level result on the credential as a whole, and individual claim errors may still exist. A credential can return `verified: true` while a specific requested claim (for example, `portrait`) is missing and surfaces under `claimErrors` with an error code of `notReturned`. The credential is trustworthy; the data payload may still be incomplete. The mirror image is `verified: false`. When the credential layer fails, MATTR VII surfaces a `reason.type` explaining why (for example, expired, revoked, untrusted issuer, invalid signer certificate, or broken device key binding). The `reason` field is typed as optional, so defensive code should handle the case where it is absent. See the [credential-level information](#credential-level-information) section above for the full list of `reason.type` values. ### Two layers of checks [#two-layers-of-checks] A relying party's business logic needs to check two distinct things: 1. **Is the credential real and valid?** Look at `verificationResult.verified` on each credential in the `credentials` array. This is the objective trust check, and it should not be overridden by business logic. 2. **Did we get all the data we asked for?** Look at `claimErrors` on each credential, and at `credentialErrors` on the top-level result. Whether a missing claim or credential is acceptable is a business-logic decision specific to your use case. In other words, `verified` is the objective measure of what can or cannot be accepted at all. Everything else, including `claimErrors`, `credentialErrors`, and the specific claim values returned, feeds your business logic about whether to accept the presentation for your particular use case. Treating `verified: true` as a green light to proceed without inspecting `claimErrors` is a common integration mistake. The credential may be cryptographically valid while still missing a field your use case requires. For example, an age-restricted purchase flow needs `birth_date`, but if only `family_name` came back the relying party cannot make an age decision even though the credential itself returned `verified: true`. Conversely, bypassing a `verified: false` result with business logic (for example, accepting an expired or revoked credential because the claims look correct) defeats the trust model and should not be done. `verified` is MATTR VII's API shape. The underlying trust evaluation for mDL and mDoc credentials follows ISO/IEC 18013-5, but the `verified` boolean itself is not a standards-defined field. ## Complete examples [#complete-examples] ### Requested credential not presented [#requested-credential-not-presented] Neither layer of the check produces a result here. The holder did not provide a matching mDL, so the credential layer has nothing to verify (no `verificationResult` is returned) and the claims layer is not applicable. The missing credential is reported under `credentialErrors`, and the relying party's business logic must decide how to handle it. ```json title="Example response for a requested credential that was not presented" { "credentialErrors": [ { "docType": "org.iso.18013.5.1.mDL", "errorCode": "notReturned" } ] } ``` ### Presented credential not verified [#presented-credential-not-verified] The credential layer fails. The holder provided an mDL but the credential-level trust checks failed (in this example, because the credential has expired): `verified: false`, with `reason.type` identifying why. Even if claims were returned, they should not be relied on while the credential itself did not verify. The credential should not be accepted regardless of business logic. ```json title="Example response for a presented credential that did not verify (expired mDL)" { "credentials": [ { "docType": "org.iso.18013.5.1.mDL", "verificationResult": { "verified": false, "reason": { "type": "MobileCredentialExpired", "message": "Credential has expired" } }, "validityInfo": { "signed": "2024-01-15T10:00:00Z", "validFrom": "2024-01-15T10:00:00Z", "validUntil": "2025-01-15T10:00:00Z", "expectedUpdate": "2026-01-15T10:00:00Z" } } ] } ``` ### Presented credential verified, all claims provided [#presented-credential-verified-all-claims-provided] Both layers pass. The credential layer returns `verified: true` with no `reason`, confirming the mDL is genuine, current, and from a trusted issuer. The claims layer returns every requested claim with no `claimErrors`, so the relying party has the complete data payload it asked for. ```json title="Example response for a presented credential that verified with all requested claims returned" { "credentials": [ { "docType": "org.iso.18013.5.1.mDL", "claims": { "org.iso.18013.5.1": { "family_name": { "value": "Smith" }, "given_name": { "value": "Jane" }, "birth_date": { "value": "1990-05-15" }, "address": { "value": "123 Main Street, Springfield" } } }, "validityInfo": { "signed": "2024-01-15T10:00:00Z", "validFrom": "2024-01-15T10:00:00Z", "validUntil": "2027-01-15T10:00:00Z", "expectedUpdate": "2028-01-15T10:00:00Z" }, "verificationResult": { "verified": true }, "issuerInfo": { "commonName": "State Department of Motor Vehicles", "trustedIssuerId": "d4a6e9f2-3b1c-4d8e-a5f7-9c2b0e8d1a3f" }, "branding": { "name": "Driver License", "description": "State-issued driver license", "backgroundColor": "#1E3A8A", "issuerLogo": { "format": "svg", "data": "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==" } } } ] } ``` ### Presented credential verified, some claims missing [#presented-credential-verified-some-claims-missing] The credential layer passes but the claims layer is incomplete. The mDL itself returns `verified: true` and is trustworthy, while one requested claim (`portrait`) was not released and appears under `claimErrors` as `notReturned`. What is missing here is data, not trust, and the relying party's business logic must decide whether the missing claim is acceptable for its use case. ```json title="Example response for a presented credential that verified with a missing claim" { "credentials": [ { "docType": "org.iso.18013.5.1.mDL", "claims": { "org.iso.18013.5.1": { "family_name": { "value": "Smith" }, "given_name": { "value": "Jane" }, "birth_date": { "value": "1990-05-15" } } }, "claimErrors": { "org.iso.18013.5.1": { "portrait": "notReturned" } }, "validityInfo": { "signed": "2024-01-15T10:00:00Z", "validFrom": "2024-01-15T10:00:00Z", "validUntil": "2027-01-15T10:00:00Z", "expectedUpdate": "2028-01-15T10:00:00Z" }, "verificationResult": { "verified": true }, "issuerInfo": { "commonName": "State Department of Motor Vehicles", "trustedIssuerId": "d4a6e9f2-3b1c-4d8e-a5f7-9c2b0e8d1a3f" } } ] } ``` ## Recommended handling flow [#recommended-handling-flow] When processing a `MobileCredentialResponse`, follow these steps: 1. **Check for credential errors**: Inspect `credentialErrors` to determine if any requested credentials were not returned by the holder. Handle these based on whether the credential is required for your use case. 2. **Iterate through credentials**: For each `MobileCredentialPresentation` in the `credentials` array: * Check `verificationResult.verified`. If `false`, inspect `verificationResult.reason` to understand why. * If `verified` is `true`, proceed to extract claim values from the `claims` object. * Check `claimErrors` for any claims that were requested but not returned. Decide whether to proceed based on which claims are missing. 3. **Apply business logic**: Use the verified claims, issuer information, and branding data to make authorization decisions and present results in your application. ## Next steps [#next-steps] * Review the mDocs Verifier SDK API reference for [iOS](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/) or [Android](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) for complete type definitions * See the [in-person verification quickstart](/docs/verification/in-person-quickstart) for building a proximity verifier * Learn how to configure [revocation status checks](/docs/verification/in-person-guides/revocation-status-check) for in-person verification flows # How to implement mDocs revocation status checks in your verifier application URL: /docs/verification/in-person-guides/revocation-status-check ## Overview [#overview] This guide demonstrates how to implement revocation status checks for mDocs in your verifier applications. By implementing status checks, your verifier can confirm whether credentials have been revoked or remain valid before accepting them. MATTR's implementation of mDocs revocation is based on Draft 14 of the IETF [Token Status List](https://drafts.oauth.net/draft-ietf-oauth-status-list/draft-ietf-oauth-status-list.html) specification. A revocable mDoc includes a reference to a status list which is managed by the issuer. The list contains the revocation status of multiple credentials, and each credential references the index of its status within a specific status list. Status lists are automatically created and managed by the issuer's MATTR VII tenant when issuing revocable mDocs. They are publicly available and mainly consumed by verifier applications to check the status of presented mDocs. For detailed information about how mDocs revocation works, including status list structure, tokens, and signing, see the [Revocation documentation](/docs/issuance/revocation/overview#mdocs). ## Prerequisites [#prerequisites] This guide builds on the [In-person Verification tutorial](/docs/verification/in-person-tutorial). It is recommended to complete that tutorial first, then return here to learn how to implement status checks. ## Understanding revocation status [#understanding-revocation-status] ### Status values [#status-values] When verifying a presented mDoc, the revocation status can be one of the following: * **Valid**: The mDoc is valid. * **Invalid**: The mDoc is permanently revoked. * **Suspended**: ⚠️ **Deprecated** ⚠️ The mDoc is temporarily revoked. Only applicable to legacy credentials issued before the adoption of Draft 14 of the Token Status List specification. * **Unknown**: The status cannot be determined, typically because the status list is unavailable or has expired. The verifier application must decide whether to accept or reject credentials with unknown status based on their security requirements. ### How status information is stored [#how-status-information-is-stored] When a revocable mDoc is issued, it includes a `status` object in its MSO payload that references a status list: ```json title="mDoc status reference" "status": { "status_list": { "uri": "https://learn.vii.au01.mattr.global/v2/credentials/mobile/status-lists/f331c9be-f526-4577-bbac-ae93d6228f7a/token", "idx": 0 } } ``` * `status_list`: References the status list that holds the status information for this mDoc. * `uri`: The publicly available endpoint where the status list token can be retrieved. * `idx`: The index of this mDoc's status within the referenced status list. When a verifier retrieves a status list, the issuer cannot tell what specific mDoc they are checking the status for. This preserves holder privacy - the issuer does not know how often or to whom an mDoc is being presented. ## Status list caching and updates [#status-list-caching-and-updates] When retrieving a status list, the response is a signed status list token (a [CBOR Web Token](https://datatracker.ietf.org/doc/html/rfc8392)) that includes: * `iat`: Timestamp when the status list token was signed. * `exp`: Expiry timestamp. * `ttl`: Recommended duration in seconds before fetching a new token. * `status_list`: The compressed status list containing the status of all mDocs included in this list. Retrieving a status list for every credential operation would create performance issues and make offline presentation impossible. To address this, MATTR uses a caching mechanism based on the `ttl` and `exp` values: * After retrieving a status list, the application should not fetch it again until the `ttl` has passed, as there are unlikely to be any changes. This optimizes performance and reduces unnecessary network requests. * If the application fails to retrieve an updated status list **after the TTL** (for example, because the device is offline), it can continue using the cached status until the status list token expiry date (`exp`). * If the **expiry date** passes without a successful update, the credential status can no longer be trusted and it is changed to `Unknown`. It is then up to the application to decide how to handle credentials with `Unknown` status. ### How the SDK manages cache updates [#how-the-sdk-manages-cache-updates] The Verifier SDK automatically calculates a `nextUpdateDate` for each cached status list token. This date is determined as the **earlier** of: * Token retrieval time + `ttl` * Token expiry time (`exp`) Based on this calculation: * **Before `nextUpdateDate`**: The SDK continues to use the cached token without attempting to fetch a new one. * **After `nextUpdateDate`**: The SDK attempts to retrieve a new status list token when status verification is performed. ### Offline behavior [#offline-behavior] When the verifier device is offline: * If the current time is **after `ttl` but before `exp`**: The SDK cannot retrieve a status list (as the device is offline), the SDK will continue to use the cached status. * If the current time is **after `exp`**: The cached status list is no longer valid, and the status will be returned as `Unknown`. This behavior ensures that verifiers don't rely on stale status information beyond the issuer's intended timeframe. ## Implementing status checks [#implementing-status-checks] By default, the Verifier SDK checks the revocation status of every credential as part of its verification workflow. This ensures that revoked credentials are not accepted. When sending a proximity presentation request, you can control whether status checks should be performed using the `checkStatus` parameter: ```swift title="Proximity presentation with status check" // Request mDoc with automatic status check (default behavior) let response = try await verifier.sendProximityPresentationRequest( request: request, checkStatus: true ) // Request mDoc without status check let response = try await verifier.sendProximityPresentationRequest( request: request, checkStatus: false ) ``` ```kotlin title="Proximity presentation with status check" // Request mDoc with automatic status check (default behavior) val response = MobileCredentialVerifier.sendProximityPresentationRequest( request, checkStatus = true ) // Request mDoc without status check val response = MobileCredentialVerifier.sendProximityPresentationRequest( request, checkStatus = false ) ``` ```typescript title="Proximity presentation with status check" // Request mDoc with automatic status check (default behavior) const response = await verifier.sendProximityPresentationRequest({ request, checkStatus: true, }); // Request mDoc without status check (for faster verification) const response = await verifier.sendProximityPresentationRequest({ request, checkStatus: false, }); ``` ### Status check flow [#status-check-flow] 1. When a proximity presentation request is sent, the SDK first checks the `checkStatus` parameter. 2. If `checkStatus` is `false`, the SDK skips status checking entirely and returns the credential without status information. 3. If `checkStatus` is `true` (the default), the SDK checks the credential's revocation status: * The SDK checks if the current time is after the cached status list's `nextUpdateDate`. * If after `nextUpdateDate`, the SDK attempts to retrieve an updated status list. * If the retrieval succeeds, the status is updated based on the new status list. * If the retrieval fails (e.g., device is offline), the SDK checks if the cached status list has expired. * There is no way to force retrieval of an updated status list if the `nextUpdateDate` hasn't been reached. The SDK will always use the cached status list until the TTL expires. 4. When using a cached status list (either because `nextUpdateDate` hasn't passed or retrieval failed): * If the cached status list hasn't expired, the status is returned from the cache. * If the cached status list has expired, the status becomes `Unknown`. Setting `checkStatus: false` will bypass status check entirely. Use this only when you need faster verification and have an alternative method to check credential status, or when operating in environments where status checking is not required. ## Proactive cache management [#proactive-cache-management] For better control over status list freshness and offline use cases, the Verifier SDK provides methods to manage the cache proactively: ### Updating status lists [#updating-status-lists] Use the SDK's revocation status list refresh method to force a refresh of all relevant status list tokens. This fetches the latest status list tokens from all trusted issuers. From iOS Verifier SDK v6.0.0 and Android Verifier SDK v7.0.0, `updateTrustedIssuerStatusLists` was renamed to `refreshRevocationStatusLists` (and `getTrustedIssuerStatusListsCacheInfo` to `getRevocationStatusListsCacheInfo`). The React Native SDK retains the original method names. See the [iOS](/docs/verification/sdks/ios-6.0.0-migration-guide) and [Android](/docs/verification/sdks/android-7.0.0-migration-guide) migration guides for details. From v6.0.0, `refreshRevocationStatusLists` returns a `RevocationStatusListsRefreshResult` `@frozen` enum with `success` and `failure` cases: ```swift title="Update status lists" do { switch try await verifier.refreshRevocationStatusLists() { case .success(let nextUpdate): // All status lists refreshed; schedule the next refresh before nextUpdate print("All status lists updated successfully") case .failure(let nextUpdate, let failedLists): // failedLists holds the URIs that failed to refresh, keyed by trusted issuer certificate ID print("Some status lists failed to update: \(failedLists)") } } catch { print("Failed to update status lists: \(error.localizedDescription)") } ``` On Android, `refreshRevocationStatusLists` returns a `RevocationStatusListsRefreshResult` sealed interface with `Success` and `Failure` variants: ```kotlin title="Update status lists" when (val result = MobileCredentialVerifier.refreshRevocationStatusLists()) { is RevocationStatusListsRefreshResult.Success -> { // All status lists refreshed successfully } is RevocationStatusListsRefreshResult.Failure -> { // result.failedLists is available here } } ``` ```typescript title="Update status lists" try { await verifier.updateTrustedIssuerStatusLists(); console.log("All status lists updated successfully"); } catch (error) { console.error("Failed to update status lists:", error); } ``` **When to use this method:** * **Before the `nextUpdateDate`**: Call this regularly (e.g., when the app starts or resumes) to ensure up-to-date verification without waiting for the `nextUpdateDate` to pass. * **During idle periods**: Update status lists when the verifier app is not actively verifying credentials. * **After connectivity is restored**: If the device was offline and is now back online, refresh the status lists. ### Inspecting cache metadata [#inspecting-cache-metadata] Use the SDK's cache info method to inspect the `nextUpdate` date for the status lists. On iOS (v6.0.0+) and Android (v7.0.0+) this is `getRevocationStatusListsCacheInfo()`; the React Native SDK retains `getTrustedIssuerStatusListsCacheInfo()`. From v6.0.0, `getRevocationStatusListsCacheInfo()` is synchronous (it throws but is not `async`) and returns a single `RevocationStatusListsCacheInfo` with an optional `nextUpdate` date: ```swift title="Inspect cache info" do { let cacheInfo = try verifier.getRevocationStatusListsCacheInfo() // nextUpdate is nil if the cache is empty or all cached status lists have expired if let nextUpdate = cacheInfo.nextUpdate, Date() > nextUpdate { print("Status lists should be updated") } } catch { print("Failed to get cache info: \(error.localizedDescription)") } ``` ```kotlin title="Inspect cache info" val cacheInfo = MobileCredentialVerifier.getRevocationStatusListsCacheInfo() val nextUpdate = cacheInfo.nextUpdate if (nextUpdate != null && nextUpdate < Clock.System.now()) { MobileCredentialVerifier.refreshRevocationStatusLists() } ``` ```typescript title="Inspect cache info" try { const cacheInfo = await verifier.getTrustedIssuerStatusListsCacheInfo(); for (const info of cacheInfo) { console.log("Issuer:", info.issuer); console.log("Next update date:", info.nextUpdateDate); console.log("Expiry date:", info.expiryDate); // Check if update is needed if (Date.now() > info.nextUpdateDate) { console.log("Status list should be updated"); } } } catch (error) { console.error("Failed to get cache info:", error); } ``` **When to use this method:** * **Monitoring cache health**: Check when the next update is due. * **Conditional updates**: Decide whether to trigger a status list refresh (`refreshRevocationStatusLists()` on iOS/Android, `updateTrustedIssuerStatusLists()` on React Native) based on the next update date (`nextUpdate` on iOS/Android, `nextUpdateDate` on React Native). ## Checking credential status [#checking-credential-status] After receiving a presentation response, you can check the status of each credential to determine whether it should be accepted: ```swift title="Checking credential status" let response = try await verifier.sendProximityPresentationRequest( request: request, checkStatus: true // checkStatus ) for credential in response.credentials { // Check the overall verification result if credential.verificationResult.verified { print("Credential verification passed") // Check the credential status if let status = credential.status { switch status { case .valid: print("Status: Valid - Accept credential") // Accept the credential case .invalid: print("Status: Invalid - Reject credential (permanently revoked)") // Reject the credential // Deprecated: .suspended only applies to legacy credentials issued before Draft 14 of the Token Status List specification case .suspended: print("Status: Suspended - Reject credential (temporarily revoked)") // Reject the credential case .unknown: print("Status: Unknown - Cannot determine status") // Handle according to your security requirements @unknown default: print("Status: Unexpected value") } } else { print("No status information available (credential may not be revocable)") // Handle non-revocable credentials } } else { print("Credential verification failed: \(credential.verificationResult.failureType?.message ?? "Unknown reason")") // Reject the credential } } ``` ```kotlin title="Checking credential status" val response = verifier.sendProximityPresentationRequest( request = request, checkStatus = true ) response.credentials.forEach { credential -> val verificationResult = credential.verificationResult if (verificationResult?.verified == true) { Log.d("Tag", "Credential is valid.") } else { when (verificationResult?.failureType) { MobileCredentialVerificationFailureType.StatusRevoked -> { Log.d("Tag", "Credential has been revoked.") // Display warning, prevent presentation } // Deprecated: StatusSuspended only applies to legacy credentials issued before Draft 14 of the Token Status List specification MobileCredentialVerificationFailureType.StatusSuspended -> { Log.d("Tag", "Credential has been suspended.") // Display warning, prevent presentation } MobileCredentialVerificationFailureType.StatusUnknown -> { Log.d("Tag", "Credential status is unknown.") // Display warning, handle according to your security requirements } else -> { // Handle other failure types } } } } ``` ```typescript title="Checking credential status" const response = await verifier.sendProximityPresentationRequest({ request, checkStatus: true, }); for (const credential of response.credentials) { // Check the overall verification result if (credential.verificationResult.verified) { console.log("Credential verification passed"); // Check the credential status if (credential.status) { switch (credential.status) { case CredentialStatus.Valid: console.log("Status: Valid - Accept credential"); // Accept the credential break; case CredentialStatus.Invalid: console.log("Status: Invalid - Reject credential (permanently revoked)"); // Reject the credential break; // Deprecated: CredentialStatus.Suspended only applies to legacy credentials issued before Draft 14 of the Token Status List specification case CredentialStatus.Suspended: console.log("Status: Suspended - Reject credential (temporarily revoked)"); // Reject the credential break; case CredentialStatus.Unknown: console.log("Status: Unknown - Cannot determine status"); // Handle according to your security requirements break; } } else { console.log("No status information available (credential may not be revocable)"); // Handle non-revocable credentials } } else { console.log(`Credential verification failed: ${credential.verificationResult.reason?.message}`); // Reject the credential } } ``` ## Handling offline scenarios [#handling-offline-scenarios] * **Cache reliance**: When offline, the SDK relies on cached status lists. * **Cached status after TTL**: If the device is offline and `ttl` has passed (but expiry hasn't), the cached status will be used. * **Unknown status after expiry**: If the device is offline and `exp` has passed, the status will always be returned as `Unknown`. * **Unknown status handling**: Define your application's policy for handling credentials with `Unknown` status. Options include: * Rejecting credentials (most secure) - Recommended for high-security scenarios. * Accepting with warnings - May be appropriate for lower-risk scenarios. * **User communication**: Clearly inform verifiers when status checks fail and provide guidance on how to proceed. # CWT credentials in-person verification journey pattern URL: /docs/verification/in-person-journey-patterns/cwt This journey pattern is used to verify a CWT credential presented in-person. ## Overview [#overview] * **Issuance channel**: In-person, Supervised * **Device/s**: Cross-device * **Formats**: CWT * **Information assurance level**: High * **Identity assurance level**: Low (can be high if holder presents an additional identity document) ## Journey flow [#journey-flow] CWT In Person Presentation ## Architecture [#architecture] CWT credentials in-person verification journey pattern ### Credential presentation [#credential-presentation] The holder uses their digital wallet (1) to select the appropriate credential to be presented to the verifier. Once selected, the wallet renders the credential as a QR code. Alternatively, the holder can also print this QR code and present a paper-based credential. ### Credential verification [#credential-verification] The verifier scans the QR code presented by the holder's wallet using a verification app (2) that can be an application built with the MATTR Pi Verifier SDK or a MATTR GO Verify branded application. The credential’s validity is checked for the following: * The digital signature is valid, indicating the content of the credential hasn’t been tampered with. * The credential hasn’t been revoked by the issuer. * The credential is currently valid and hasn’t expired yet (based on valid from and valid until values included in the credential). * It has been issued by an issuer that is trusted to issue this type of credential according to the Digital Trust Service (3). The only reliance the verification has on the issuer is calling an online revocation list (if the credential has revocation properties), which the credential issuer may host to check the status of the credential being presented. No information relating to the verifier or the context in which the holder is utilizing the credential is shared or available to the issuer. Verification can also be achieved by extracting the credential payload and passing it through to a MATTR VII verifier tenant. # In-person IDV integration pattern URL: /docs/verification/in-person-journey-patterns/idv-integration This journey pattern is used to verify a credential presented via a [proximity verification workflow](/docs/verification/in-person-overview) as part of an in-person high-assurance interaction. ## Overview [#overview] * **Issuance channel**: In-person, Supervised * **Device/s**: Cross-device * **Formats**: mDocs * **Information assurance level**: Very High * **Identity assurance level**: High (exact identity assurance levels depends on specific IDV blocks implemented in the workflow). ## Journey flow [#journey-flow] In-person IDV integration pattern ## Architecture [#architecture] In-person IDV architecture ### Establishing the connection [#establishing-the-connection] The user uses their digital wallet (1) to choose an mDL to present, which renders a QR code on the Holder’s device screen. The verifier application (2) can be an application built with the MATTR Pi Verifier SDK or a MATTR GO Verify branded application. It is used to scan the presented QR code and set up a secure Bluetooth Low Energy (BLE) connection with the user’s device. ### Sending a presentation request [#sending-a-presentation-request] Once the Bluetooth connection is established, the verifier requests specific information by sending a presentation request. This request contains all the information needed for the digital wallet or holding device to understand: * What information is being requested. * Who is asking for it. * Where the wallet should respond to. * Required assurance levels. ### Reviewing the presentation request [#reviewing-the-presentation-request] The digital wallet (1) will locate any stored credentials that match the information specified in the request and present them to the user, indicating what information is requested by the verifier. ### Sharing the information [#sharing-the-information] The user consents to sharing the information, which is then bundled by the wallet as a verifiable presentation, cryptographically signed, and sent to the verifier application as a presentation response. ### Verifying the information [#verifying-the-information] The verifier application (2) interprets and verifies the credentials: * The digital signature is valid, indicating the content of the credential hasn't been tampered with. * It hasn’t been revoked or suspended by the issuer. * The credential is currently valid and hasn't expired yet (based on valid from and valid until values included in the credential). * It’s been issued by an issuer that is trusted to issue this type of credential according to information retrieved from the Digital Trust Service (3). Verification results are then displayed by the verifier application, enabling the physical interaction to continue accordingly. # mDocs in-person verification journey pattern URL: /docs/verification/in-person-journey-patterns/mdocs This journey pattern is used to verify an mDoc in-person via a [proximity verification workflow](/docs/verification/in-person-overview), as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). ## Overview [#overview] * **Issuance channel**: In-person, supervised * **Device/s**: Cross-device * **Formats**: mDocs * **Information assurance level**: High * **Identity assurance level**: High ## Journey flow [#journey-flow] mDocs In Person Presentation ## Architecture [#architecture] Enrolment architecture ### Establishing the connection [#establishing-the-connection] The user opens their digital wallet app on their mobile device and selects a credential they wish to present. They then establish a secure connection with the verifier in one of the following ways: The wallet app displays a QR code on the screen. The verifier application, which may be operated by a person or run on a self-service kiosk, scans this QR code using its device. Scanning the QR code initiates a secure Bluetooth Low Energy (BLE) connection between the two devices. On Android devices, the BLE connection can also be initiated via NFC tap. ### Sending a presentation request [#sending-a-presentation-request] Once the secure connection is established, the verifier application sends a presentation request to the wallet app. This request includes: * The specific information being requested (e.g. claims or attributes from a credential). * The identity of the verifier making the request. * The required assurance level for the data. * Instructions on how and where the response should be returned. ### Reviewing the presentation request [#reviewing-the-presentation-request] The wallet app processes the presentation request and identifies any matching credentials stored on the device. It presents this information to the user, showing: * Which credential(s) can satisfy the request. * Exactly what data will be disclosed if the user proceeds. * Whether selective disclosure is available for that credential, allowing the user to share only the requested claims. * Whether the verifier is trusted, using information from a Digital Trust Service configured for the current trust network. ### Sharing the information [#sharing-the-information] If the user consents, the wallet app: * Creates a verifiable presentation using the selected credential and requested data. * Cryptographically signs the presentation. * Sends the signed presentation back to the verifier app over the BLE connection. ### Verifying the information [#verifying-the-information] The verifier application receives the presentation and performs a series of verification checks, including: * Validating the digital signature to confirm the data has not been tampered with. * Checking that the credential has not been revoked or suspended, using a revocation list (if applicable). * Verifying that the credential is currently valid, based on its “valid from” and “valid until” dates. * Ensuring the credential was issued by a trusted issuer, based on information retrieved from a Digital Trust Service. The issuer of the credential is not informed that the presentation has occurred. No data about the verifier, the context of use, or the interaction itself is shared with the issuer. The only interaction with the issuer is a potential call to an online revocation endpoint, if revocation checking is required. # mDocs remote mobile verification journey pattern URL: /docs/verification/remote-mobile-verifiers/journey-pattern This journey pattern is used to verify an mDoc remotely by presenting it to an app installed on the same mobile device as the digital wallet, as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). ## Overview [#overview] * **Issuance channel**: Remote, unsupervised * **Device/s**: Same-device * **Formats**: mDocs * **Information assurance level**: High * **Identity assurance level**: High ## Journey flow [#journey-flow] mDocs Mobile Verification ## Architecture [#architecture] Remote verification mobile architecture ### Interacting with the mobile application [#interacting-with-the-mobile-application] The user accesses a mobile app that embeds the MATTR Pi Verifier Mobile SDK. The app initiates and handles the entire verification flow on the same device. ### Requesting a credential for verification [#requesting-a-credential-for-verification] The Verifier Mobile SDK sends a verification request to a configured MATTR VII verifier tenant, defining: * The credentials and claims required * The supported interaction mode (same-device) The MATTR VII verifier tenant is configured with: * Which apps or domains can issue verification requests * The workflows it supports (same-device and/or cross-device) * The protocols it supports (e.g. OID4VP, Apple’s Verify with Wallet API) * Which wallet applications it can invoke on the same device The verifier tenant responds with a custom URI or universal link. The Verifier Mobile SDK uses this to launch the wallet app directly. ### Presenting request details to the user [#presenting-request-details-to-the-user] The wallet retrieves the presentation request and displays: * The credentials requested * The claims that will be shared * Whether the relying party is trusted by the Digital Trust Service * Which of the user’s credentials match the request The user authenticates and consents to share the requested information. ### Verifying the credential [#verifying-the-credential] The MATTR VII verifier tenant verifies the credential by checking: * Validating the digital signature to confirm the data has not been tampered with * Checking that the credential has not been revoked or suspended, using a revocation list (if applicable) * Verifying that the credential is currently valid, based on its “valid from” and “valid until” dates * Ensuring the credential was issued by a trusted issuer, based on information retrieved from a Digital Trust Service The issuer of the credential is not informed that the presentation has occurred. No data about the verifier, the context of use, or the interaction itself is shared with the issuer. The only interaction with the issuer is a potential call to an online revocation endpoint, if revocation checking is required. ### Displaying verification results [#displaying-verification-results] Once verification is complete, the wallet app redirects the user back to the mobile application using the provided redirect URI. The Verifier Mobile SDK receives the result and displays it within the app interface. The MATTR VII verifier tenant can also be configured to share the verification results with a configured back-end rather than the front-end directly. # Remote mobile verification quickstart guide URL: /docs/verification/remote-mobile-verifiers/quickstart This quickstart is for evaluating the [MATTR Pi mDocs Mobile Verifier SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview). In about 15-20 minutes you will configure a MATTR VII verifier tenant, run a sample verifier mobile app (iOS, Android, or React Native) on a physical device, and use it to verify an mDoc presented from a wallet application on the same device using the [remote presentation workflow](/docs/verification/remote-overview) defined by [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) and [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) Annex B. This is an app-to-app flow. The verifier app invokes a separate wallet app installed on the same device to request the credential, and the wallet returns the presentation to the verifier. It is not the browser-based Digital Credentials API flow: both apps run on the same physical device and exchange the OID4VP request and response directly between each other. **Estimated time: 15-20 minutes.** This covers the verifier setup and verification flow. If you are using the sample holder app, allow additional 10-15 minutes to build and run it from the [Holder SDK quickstart guide](/docs/holding/sdk-quickstart), which you need before [Part 3](#part-3-claim-and-verify-a-credential-5-minutes). Use this guide as a fast evaluation path to see the Mobile Verifier SDK working end-to-end on a real device. For detailed information and API examples, explore the [tutorial](/docs/verification/remote-mobile-verifiers/tutorial) and reference documentation tailored for each platform. ## User experience [#user-experience] In this quickstart you will perform this exact flow yourself using a MATTR VII verifier tenant, the sample verifier app, and a wallet that supports remote presentation acting as the holder: Remote mobile app verification overview 1. A relying party uses the Mobile Verifier SDK to embed a remote verification workflow into a mobile application. 2. When a user interacts with the mobile application, a matching wallet application installed on their mobile device is invoked to request an mDoc for verification. 3. The user consents to sharing the requested information. 4. The user's wallet application shares the matching mDoc with the MATTR VII tenant configured by the Mobile Verifier SDK to perform the verification workflow. 5. The MATTR VII tenant performs the required checks and returns the verification results via the Mobile Verifier SDK to the verifier application. 6. The user journey continues based on the verification results. ## Prerequisites [#prerequisites] * Use the [Get Started form](/docs/resources/get-started) to request a trial of MATTR **verification** capabilities. You will receive access to the following resources: * MATTR Pi mDocs Verifier SDK for your chosen platform (iOS, Android, or React Native). * MATTR VII tenant. * A physical mobile device to run the verifier app, with a wallet that supports remote presentation as per ISO/IEC 18013-7 and OID4VP installed on the **same device**. You can build and run the sample holder app from the [Holder SDK quickstart guide](/docs/holding/sdk-quickstart). * Development environment set up for your chosen platform (Xcode for iOS, Android Studio for Android, or a React Native / Expo development environment with Node.js 18+). ## Steps [#steps] In this quickstart you will: 1. Configure a MATTR VII verifier tenant to handle the verification requests. 2. Download, configure, and run a local sample verifier project for your platform. 3. Claim a test credential into a wallet on the same device and verify it remotely. 4. Review the structure of the sample project so you can see how to integrate the SDK into your own app. Use the tabs below to follow platform-specific setup steps. ## Part 1: Configure the MATTR VII verifier tenant (5 minutes) [#part-1-configure-the-mattr-vii-verifier-tenant-5-minutes] These steps configure your verifier tenant so the sample verifier app can request and verify mDocs issued by a trusted issuer. ### Create a MATTR VII tenant [#create-a-mattr-vii-tenant] If you already have a tenant you can skip this step. 1. Log into the [MATTR Portal](https://portal.mattr.global). 2. Select the **Create/switch tenant** button on the top-right side of the screen.\ The *All tenants* panel is displayed, listing any existing tenants. 3. Select the **Create new** button.\ The *New tenant* form is displayed. 4. Use the *Region* dropdown list to select the region your tenant will be hosted in. 5. Use the *Tenant subdomain* text box to insert a subdomain for your tenant (e.g. `remote-mobile-verification`). 6. Select the **Create** button to create the new tenant. 7. Copy the displayed tenant information (`audience`, `auth_url`, `tenant_url`, `client_id` and `client_secret`) which is required for the next step. ### Create a verifier application configuration [#create-a-verifier-application-configuration] Create a Verifier Application on your MATTR VII tenant so the Mobile Verifier SDK can tether to it and request credential presentations: [Make a request](/docs/api-reference#getting-started) of the following structure to create an iOS Verifier Application configuration on your MATTR VII tenant **(make sure to replace `bundleId`, `teamId` and `redirectUri` with your own values)**: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My iOS Mobile Verifier Application", "type": "ios", "bundleId": "com.yourname.mobileverifier", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" }, "openid4vpConfiguration": { "redirectUri": "com.yourname.mobileverifier://my/path" } } ``` * `bundleId`: This must match the bundle identifier of your iOS application you will create in the next step. * `teamId`: This must match the Apple Developer Team ID that actually signs this app. * `redirectUri`: The URI the user is redirected to after presenting the credential from their wallet. Structure it as `{your_app_bundleId}://my/path`. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", // [!code focus] "name": "My iOS Mobile Verifier Application", "type": "ios", // ... rest of application configuration } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. [Make a request](/docs/api-reference#getting-started) of the following structure to create an Android Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Android Mobile Verifier Application", "type": "android", "packageName": "com.yourname.mobileverifier", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false }, "openid4vpConfiguration": { "redirectUri": "com.yourname.mobileverifier://oid4vp-callback" } } ``` A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", // [!code focus] "name": "My Android Mobile Verifier Application", "type": "android", // ... rest of application configuration } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. A React Native app runs on both iOS and Android, and the MATTR VII tenant validates requests differently for each platform. Create a Verifier Application for **each platform you intend to run on** by following the **iOS** and **Android** tabs above. Each platform's `redirectUri` must match that platform's URL scheme, so configure one Verifier Application per platform and use the matching application `id` at runtime. ### Create a supported wallet configuration [#create-a-supported-wallet-configuration] This defines the URI scheme the MATTR VII tenant uses to invoke the wallet application as part of the remote verification workflow. 1. Log into your MATTR VII tenant in the [MATTR Portal](https://portal.mattr.global/). 2. Expand the **Credential Verification** menu in the left-hand navigation panel. 3. Select **Supported wallets**. 4. Select **Create new**. 5. Enter a meaningful *Name* for the supported wallet (e.g. "My Supported Wallet"). 6. Enter `mdoc-openid4vp://` in the *Authorization Endpoint* field. This is the default OID4VP scheme used to invoke the wallet application. 7. Select **Create**. More information on applying different URI schemes and the resulting user experience can be found on the [workflow page](/docs/verification/remote-mobile-verifiers/workflow#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application). ### Configure a trusted issuer [#configure-a-trusted-issuer] In this quickstart you will add a MATTR-provided demo issuer certificate so your verifier accepts mDocs issued by this issuer. 1. Select **Trusted issuers** under the **Credential Verification** menu. 2. Select **Create new**. 3. Copy and paste the following certificate into the *Certificate PEM file* field: ``` -----BEGIN CERTIFICATE----- MIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG EwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew HhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp MCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp dB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq 232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu bWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp ZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp bGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny bDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI SNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6 -----END CERTIFICATE----- ``` 4. Select **Add**. ## Part 2: Configure and run the sample verifier app (5-10 minutes) [#part-2-configure-and-run-the-sample-verifier-app-5-10-minutes] In this section you will download, configure, and run a sample verifier app that uses the Mobile Verifier SDK. This sample app is the completed result of the [remote mobile verification tutorial](/docs/verification/remote-mobile-verifiers/tutorial). 1. Access the sample verifier codebase by either: * Cloning the [MATTR Sample Apps repository](https://github.com/mattrglobal/sample-apps): ```bash title="Clone sample apps" git clone https://github.com/mattrglobal/sample-apps.git ``` * Or downloading just the iOS sample verifier project using [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Fios-remote-verification-tutorial-sample-app). 2. Use Xcode to open the `.xcodeproj` file in the project's folder. You can find it in the `sample-apps/ios-remote-verification-tutorial-sample-app` directory. 3. Drag the `MobileCredentialVerifierSDK.xcframework` folder (obtained from MATTR as part of the SDK package) into your project. 4. Configure `MobileCredentialVerifierSDK.xcframework` to [Embed & sign](https://help.apple.com/xcode/mac/current/#/dev51a648b07). 5. Set the bundle identifier you entered in [Part 1](#part-1-configure-the-mattr-vii-verifier-tenant-5-minutes) for the project in the Xcode project settings (e.g. `com.yourname.mobileverifier`). 6. Open the `Constants` file and update it with your tenant and application details: ```swift title="Constants" enum Constants { static let tenantHost = URL(string: "https://your-tenant.vii.mattr.global")! static let applicationID = "your-application-id" } ``` * `tenantHost` : Replace with the URL of your MATTR VII tenant, available in the MATTR Portal under **Platform Management > Tenant**. * `applicationID` : Replace with the `id` returned when you created the verifier application in [Part 1](#create-a-verifier-application-configuration). 7. Run the project on a physical iOS device. 1. Access the sample verifier codebase by either: * Cloning the [MATTR Sample Apps repository](https://github.com/mattrglobal/sample-apps): ```bash title="Clone sample apps" git clone https://github.com/mattrglobal/sample-apps.git ``` * Or downloading just the Android sample verifier project using [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Fandroid-remote-verification-tutorial-sample-app). 2. Open the project in Android Studio. You can find it in the `sample-apps/android-remote-verification-tutorial-sample-app` directory. 3. Unzip the `mobile-credential-verifier-*version*.zip` file (obtained from MATTR as part of the SDK package) and copy the unzipped `global` folder into the project's `repo` folder. 4. Sync the project with Gradle files to recognize the new module. 5. Open `app/build.gradle.kts` and replace the placeholder `applicationId` with the package name you entered in [Part 1](#part-1-configure-the-mattr-vii-verifier-tenant-5-minutes) (e.g. `com.yourname.mobileverifier`). 6. Open the `Constants` file and update it with your tenant and application details: ```kotlin title="Constants" object Constants { const val TENANT_HOST = "https://your-tenant.vii.mattr.global" const val APPLICATION_ID = "your-application-id" } ``` * `TENANT_HOST` : Replace with the URL of your MATTR VII tenant, available in the MATTR Portal under **Platform Management > Tenant**. * `APPLICATION_ID` : Replace with the `id` returned when you created the verifier application in [Part 1](#create-a-verifier-application-configuration). 7. Run the project on a physical Android device. 8. Retrieve your app's signing certificate thumbprint and convert it to the required format: * Open a terminal inside your project's root folder and run: ```bash title="Retrieve signing certificate" ./gradlew signingReport ``` If you see `permission denied: ./gradlew`, the Gradle wrapper has lost its executable bit (this happens when the project is downloaded as a zip rather than cloned). Make it executable and run the command again: ```bash title="Fix wrapper permissions" chmod +x gradlew ``` * Locate the `SHA-256` value in the `Variant: debug` section, remove all colons (`:`), and convert all letters to lowercase. The result is the thumbprint you will send to your tenant in the next step: ```bash title="Example conversion" echo '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5' | tr -d ':' | tr 'A-F' 'a-f' ``` This thumbprint is only valid for your debug builds. If you intend to publish your app, repeat this process with the release signing certificate. Refer to [Android App Signing](/docs/verification/android-app-signing) for more information. 9. Update your verifier application with the formatted thumbprint by making a request of the following structure. Use the application `id` returned in [Part 1](#create-a-verifier-application-configuration) as the `{applicationId}` path parameter, and set `packageSigningCertificateThumbprints` to the thumbprint you generated in the previous step: ```http title="Request" PUT /v2/presentations/applications/{applicationId} ``` ```json title="Request body" { "name": "My Android Mobile Verifier Application", "type": "android", "packageName": "com.yourname.mobileverifier", "packageSigningCertificateThumbprints": [ "91f7cbf9d681531bc7a58fb833cca14dabede509c5" ], "keyAttestation": { "required": false }, "openid4vpConfiguration": { "redirectUri": "com.yourname.mobileverifier://oid4vp-callback" } } ``` The `PUT` request replaces the entire application configuration, so include all the fields you set when you created the application in [Part 1](#create-a-verifier-application-configuration), not just the thumbprint. A successful response returns a `200` status code with the updated Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", "name": "My Android Mobile Verifier Application", "type": "android", "packageSigningCertificateThumbprints": [ "91f7cbf9d681531bc7a58fb833cca14dabede509c5" ], // ... rest of application configuration } ``` 1. Access the sample verifier codebase by either: * Cloning the [MATTR Sample Apps repository](https://github.com/mattrglobal/sample-apps): ```bash title="Clone sample apps" git clone https://github.com/mattrglobal/sample-apps.git ``` * Or downloading just the React Native sample verifier project using [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Freact-native-remote-verification-tutorial%2Freact-native-remote-verification-tutorial-complete). 2. Open the project in your code editor. You can find it in the `sample-apps/react-native-remote-verification-tutorial/react-native-remote-verification-tutorial-complete` directory. 3. Open `app.config.ts` and set the app identifiers, each under its marker comment, to the bundle ID / package name you entered in [Part 1](#part-1-configure-the-mattr-vii-verifier-tenant-5-minutes) (e.g. `com.yourname.mobileverifier`): * Update the `bundleIdentifier` (iOS) value under the `// Update the bundle identifier` comment: ```ts title="app.config.ts" bundleIdentifier: "com.yourname.mobileverifier", ``` * Update the `package` (Android) value under the `// Update the package name` comment: ```ts title="app.config.ts" package: "com.yourname.mobileverifier", ``` * Add the iOS custom URL scheme under the `// Update the scheme property` comment. This registers the scheme the wallet uses to redirect back to your app on iOS devices. Set it to your iOS bundle identifier: ```ts title="app.config.ts" scheme: "com.yourname.mobileverifier", ``` 4. Open the `Constants.ts` file and update it with your tenant and application details: ```ts title="Constants.ts" import { Platform } from "react-native"; /** * Configuration values used to initialize the SDK and request credentials. * * Replace these placeholders with your own values before running the app: * - `TENANT_HOST`: the URL of your MATTR VII tenant, available in the MATTR Portal under * Platform Management > Tenant. * - `IOS_APPLICATION_ID` / `ANDROID_APPLICATION_ID`: the `id` returned when you created the verifier * application configuration on your MATTR VII tenant (see Part 1). * iOS and Android each use their own verifier application, because the redirect URI registered on * the application must match that platform's URL scheme (see `app.config.ts`). Configure one * application ID per platform. */ const IOS_APPLICATION_ID = "your-ios-application-id"; const ANDROID_APPLICATION_ID = "your-android-application-id"; export const Constants = { TENANT_HOST: "https://your-tenant.vii.mattr.global", // Resolves to the verifier application ID for the current platform. APPLICATION_ID: Platform.OS === "ios" ? IOS_APPLICATION_ID : ANDROID_APPLICATION_ID, }; ``` * `TENANT_HOST` : Replace with the URL of your MATTR VII tenant, available in the MATTR Portal under **Platform Management > Tenant**. * `IOS_APPLICATION_ID` and `ANDROID_APPLICATION_ID` : Replace the constant for the platform you are running with the `id` returned when you created the verifier application in [Part 1](#create-a-verifier-application-configuration). To run on both platforms, create a verifier application for each (see the callout in [Part 1](#create-a-verifier-application-configuration)) and set both constants. * `APPLICATION_ID` : Resolves to the verifier application `id` for the current platform at runtime. The SDK reads this value when requesting credentials. 5. With the app files configured, install the dependencies: ```bash title="Install dependencies" yarn install ``` Generate the native project files: ```bash title="Generate project files" yarn expo prebuild ``` Then run the app on a physical device for your target platform: ```bash title="Run iOS application" yarn ios --device ``` ```bash title="Run Android application" yarn android --device ``` 6. **Android only**: Retrieve your app's signing certificate thumbprint and update the verifier application configuration: ```bash title="Retrieve signing certificate" cd android && ./gradlew signingReport ``` Locate the `SHA-256` value in the `Variant: debug` section, remove all colons (`:`), and convert all letters to lowercase: ```bash title="Example conversion" echo '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5' | tr -d ':' | tr 'A-F' 'a-f' ``` Update your verifier application with this thumbprint by making a [`PUT /v2/presentations/applications/{applicationId}`](/docs/verification/remote-verification-api-reference/verifier-applications#update-a-verifier-application) request to your MATTR VII tenant, setting `packageSigningCertificateThumbprints` to the value you just generated (use the Android application `id` returned in [Part 1](#create-a-verifier-application-configuration)). This thumbprint is only valid for your debug builds. If you intend to publish your app, repeat this process with the release signing certificate. Refer to [Android App Signing](/docs/verification/android-app-signing) for more information. ## Part 3: Claim and verify a credential (5 minutes) [#part-3-claim-and-verify-a-credential-5-minutes] ### Claim a credential to present [#claim-a-credential-to-present] To test the verification flow, you need a compatible mDoc in a wallet that supports remote presentation on the same device as the verifier app. Use the sample holder app you build with the [Holder SDK quickstart guide](/docs/holding/sdk-quickstart) to claim a demo mDL credential issued by the MATTR demo issuer you added as a trusted issuer in [Part 1](#configure-a-trusted-issuer). 1. Build and run the sample holder app on the device running the sample verifier app, following [Part 1 of the Holder SDK quickstart guide](/docs/holding/sdk-quickstart#part-1-configure-the-sample-holder-project-5-10-minutes). 2. In the sample holder app, select **Claim Credential** (you may need to allow the app to access your camera). 3. Scan the following QR code: QR Code 4. Follow the on-screen instructions to claim the credential into the sample holder app. This credential will be used in the next step when you test the remote verification flow. ### Verify the credential [#verify-the-credential] 1. Launch the sample verifier app and select **Request credentials**. 2. You will be redirected to the sample holder app, which displays what information is requested for verification. 3. Select the credential you claimed in the previous step and consent to sharing the requested information. 4. You will be redirected back to the sample verifier app, which displays the verification results. Behind the scenes, the Mobile Verifier SDK started a presentation session with your MATTR VII tenant, invoked the wallet, and surfaced the verification results returned by the tenant according to OID4VP and ISO/IEC 18013-7:2025 Annex B. ## Review the codebase [#review-the-codebase] The sample verifier application uses the [Mobile Verifier SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) to verify mDocs presented remotely from a wallet on the same device. Once you have the sample application running, use this section to inspect the key SDK calls you will reuse in your own application. ### Initialize the SDK [#initialize-the-sdk] The SDK is initialized with your tenant host configuration so it knows which MATTR VII tenant to interact with. ```swift title="Create platform configuration" let platformConfiguration = PlatformConfiguration( tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, applicationId: "your-application-id" ) ``` `initialize` is asynchronous from v6.0.0 (call it from an asynchronous context). It registers the app instance with your tenant and obtains a license, so it can throw `failedToRegister` and `invalidLicense`: ```swift title="Initialize the SDK" mobileCredentialVerifier = MobileCredentialVerifier.shared try await mobileCredentialVerifier.initialize(platformConfiguration: platformConfiguration) ``` * `tenantHost` : URL of your MATTR VII tenant. * `applicationId` : The `id` returned when you created the verifier application configuration. Supplied here via `PlatformConfiguration` (no longer passed to `requestMobileCredentials`) and used for [SDK Tethering](/docs/verification/sdks/sdk-tethering). ```kotlin title="Create platform configuration" val platformConfiguration = PlatformConfiguration( tenantHost = URL("https://your-tenant.vii.mattr.global"), applicationId = "your-application-id" ) ``` `initialize` registers the app instance with your tenant and obtains a license, so it can throw `FailedToRegisterException` and `InvalidLicenseException`: ```kotlin title="Initialize the SDK" MobileCredentialVerifier.initialize(context, platformConfiguration) ``` * `tenantHost` : URL of your MATTR VII tenant. * `applicationId` : The `id` returned when you created the verifier application configuration. Supplied here via `PlatformConfiguration` (no longer passed to `requestMobileCredentials`) and used for [SDK Tethering](/docs/verification/sdks/sdk-tethering). ```tsx title="Create platform configuration and initialize" import { initialize } from "@mattrglobal/mobile-credential-verifier-react-native"; const result = await initialize({ platformConfiguration: { tenantHost: "https://your-tenant.vii.mattr.global", }, }); if (result.isErr()) { console.error("Failed to initialize SDK:", result.error); } ``` * `tenantHost` : Base URL of your MATTR VII tenant. ### Create a presentation request [#create-a-presentation-request] A presentation request defines the type of credential and the specific data elements being requested for verification. ```swift title="Create a presentation request" let mobileCredentialRequest = MobileCredentialRequest( docType: "org.iso.18013.5.1.mDL", namespaces: [ "org.iso.18013.5.1": [ "family_name": false, "given_name": false, "birth_date": false ] ] ) ``` * `docType` : The type of credential being requested (e.g. `org.iso.18013.5.1.mDL`). * `namespaces` : A dictionary mapping each namespace to its requested attributes. Each attribute is a key with a Boolean indicating whether the verifier intends to retain this attribute (`true`) or not (`false`). ```kotlin title="Create a presentation request" val mobileCredentialRequest = MobileCredentialRequest( docType = "org.iso.18013.5.1.mDL", namespaces = NameSpaces( mapOf( "org.iso.18013.5.1" to DataElements( mapOf( "family_name" to false, "given_name" to false, "birth_date" to false ) ) ) ) ) ``` * `docType` : The type of credential being requested (e.g. `org.iso.18013.5.1.mDL`). * `namespaces` : A map of each namespace to its requested attributes. Each attribute is a key with a Boolean indicating whether the verifier intends to retain this attribute (`true`) or not (`false`). ```tsx title="Create a presentation request" const mobileCredentialRequest = { docType: "org.iso.18013.5.1.mDL", namespaces: { "org.iso.18013.5.1": { family_name: false, given_name: false, birth_date: false, }, }, }; ``` * `docType` : The type of credential being requested (e.g. `org.iso.18013.5.1.mDL`). * `namespaces` : An object mapping each namespace to its requested attributes. Each attribute is a key with a Boolean indicating whether the verifier intends to retain this attribute (`true`) or not (`false`). ### Request credentials from the wallet application [#request-credentials-from-the-wallet-application] This starts a presentation session with the MATTR VII tenant and redirects the user to a matching wallet application. ```swift title="Request credentials" let onlinePresentationResult = try await mobileCredentialVerifier.requestMobileCredentials( request: [mobileCredentialRequest] ) ``` * `request` : Array of `MobileCredentialRequest` objects defining what information to request. From v6.0.0, the `applicationId` is no longer passed here — it comes from the `PlatformConfiguration` you set when initializing the SDK. The `challenge` parameter is now optional: omit it to let the SDK generate a secure challenge, or pass your own unique value per session to mitigate replay attacks. ```kotlin title="Request credentials" val onlinePresentationResult = MobileCredentialVerifier.requestMobileCredentials( activity = activity, request = listOf(mobileCredentialRequest) ) ``` * `activity` : Defines the current activity context. * `request` : List of `MobileCredentialRequest` objects defining what information to request. From v7.0.0, the `applicationId` is no longer passed here — it comes from the `PlatformConfiguration` you set when initializing the SDK. ```tsx title="Request credentials" const result = await requestMobileCredentials({ request: [mobileCredentialRequest], applicationId: Constants.APPLICATION_ID, challenge: Crypto.randomUUID(), }); ``` * `request` : Array of `MobileCredentialRequest` objects defining what information to request. * `applicationId` : The `id` returned when you created the verifier application configuration. * `challenge` : A unique, unpredictable value to identify this presentation session and prevent replay attacks. Generate a new challenge for every request. ### Handle the redirect from the wallet [#handle-the-redirect-from-the-wallet] After the wallet presents the credential, the user is redirected back to the verifier app via the custom URL scheme configured for the sample app in [Part 2](#part-2-configure-and-run-the-sample-verifier-app-5-10-minutes). ```swift title="Handle redirect URL" .onOpenURL { url in // Navigate to response screen viewModel.navigationPath.append(NavigationState.viewResponse) viewModel.mobileCredentialVerifier.handleDeepLink(url) } ``` Add this modifier to your SwiftUI view to navigate to the response screen and pass the redirect URL to the SDK's `handleDeepLink` method. The SDK processes the response and updates the `receivedDocuments` variable with the verification results. The SDK handles the redirect via the `Openid4VpCallbackActivity` declared in your `AndroidManifest.xml`. Register the callback activity with an intent filter that matches the redirect URI of your verifier application: ```xml title="AndroidManifest.xml" ``` * `android:scheme` : Must match the redirect custom scheme you defined when you created the verifier application. * `android:host` : Must match the redirect host you defined when you created the verifier application. Handling the redirect differs by platform: * **iOS**: Forward the wallet's redirect URL to the SDK with `handleDeepLink` so it can complete the pending `requestMobileCredentials` call: ```tsx title="Handle redirect URL (iOS)" useEffect(() => { if (Platform.OS !== "ios") { return; } const subscription = Linking.addEventListener("url", ({ url }) => { handleDeepLink({ url }); }); return () => subscription.remove(); }, []); ``` * **Android**: The `withOpenid4VpCallbackActivity` config plugin declares the SDK's `Openid4VpCallbackActivity` in `AndroidManifest.xml`, bound to `{your_packageName}://oid4vp-callback`. The SDK handles the redirect automatically, so `requestMobileCredentials` resolves directly and no additional code is required. ### Handle the presentation response [#handle-the-presentation-response] The [`requestMobileCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialverifier/requestmobilecredentials\(request:challenge:walletproviderid:\)) method returns an `OnlinePresentationSessionResult` object containing the presentation details, any errors encountered, and the verification status of the credentials. The sample app reads the credentials from this result, stores them, and renders them in a dedicated view. From v6.0.0, `OnlinePresentationSessionResult` is a `@frozen` enum with `success` and `failure` cases, so you branch over it with `switch`/`case` instead of inspecting nullable fields: ```swift title="Handle the response" switch onlinePresentationResult { case .success(_, _, let mobileCredentialResponse): receivedDocuments = mobileCredentialResponse?.credentials ?? [] case .failure(_, _, let error): print("No response received: \(error.message)") return } ``` * `success` : Carries `sessionId`, `challenge`, and an optional `mobileCredentialResponse` containing the verified credentials and their claims. * `failure` : Carries `sessionId`, `challenge`, and an `error` (`type` and `message`) describing what went wrong. * `credentials` : A list of presented credentials with their data, status, and verification results. The sample app assigns the result to the `receivedDocuments` variable, which a `DocumentView` then renders. A `DocumentViewModel` converts each `MobileCredentialPresentation` (docType, verification result, claims, and claim errors) into a human-readable format for display. The [`requestMobileCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-mobile-credential-verifier/request-mobile-credentials.html) method returns an [`OnlinePresentationSessionResult`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-online-presentation-session-result/index.html) object. This response contains the presentation details provided by the holder, including any errors encountered and the verification status of the credentials: From v7.0.0, `OnlinePresentationSessionResult` is a sealed interface with `Success` and `Failure` variants: ```kotlin title="OnlinePresentationSessionResult structure" sealed interface OnlinePresentationSessionResult { val sessionId: String val challenge: String? data class Success( override val sessionId: String, override val challenge: String? = null, val mobileCredentialResponse: MobileCredentialResponse? ) : OnlinePresentationSessionResult data class Failure( override val sessionId: String, override val challenge: String? = null, val error: OnlinePresentationResultError ) : OnlinePresentationSessionResult } ``` Branch over the result with `when`/`is` instead of inspecting nullable fields: ```kotlin title="Handle the response" when (onlinePresentationResult) { is OnlinePresentationSessionResult.Failure -> { println("No response received: ${onlinePresentationResult.error.message}") } is OnlinePresentationSessionResult.Success -> { val receivedCredentials = onlinePresentationResult.mobileCredentialResponse?.credentials.orEmpty() // Process the received credentials for (credential in receivedCredentials) { println("DocType: ${credential.docType}") println("Verified: ${credential.verificationResult.verified}") credential.claims?.forEach { (namespace, elements) -> elements.forEach { (elementId, value) -> println("$namespace.$elementId: ${value.value}") } } } } } ``` * `Success.mobileCredentialResponse` : Contains the verified credentials and their claims. * `Failure.error` : The error that occurred during the verification process. * `credentials` : A list of presented credentials with their data, status, and verification results. The sample app assigns the result to the `_receivedDocuments` state flow, which a `DocumentView` composable then renders. The composable reads the docType and verification result and flattens `document.claims` into a list of label and value pairs for display. ```tsx title="Handle the response" const session = result.value; if (!session.isSuccess) { console.error(`Verification session failed: ${session.error.message}`); return; } const response = session.mobileCredentialResponse; setVerificationResults(response ?? null); ``` * `result.value` : The `OnlinePresentationSessionResult`. Check `session.isSuccess` to distinguish a successful session from a failed one (`session.error`). * `mobileCredentialResponse` : Contains the verified credentials and their claims. * `credentials` : A list of presented credentials with their data, status, and verification results. The sample app stores the response and renders it in a `VerificationResultsModal`, which reads each `MobileCredentialPresentation` (docType, verification result, claims, and claim errors) and displays it in a human-readable format. ## Next steps [#next-steps] * For your evaluation: * Note how long it took to configure the tenant, run the sample app, and complete a remote verification. * Consider how the sample's presentation request and result handling map to your real verification use cases (for example, which attributes you would request and how you would display them). * Explore the detailed [tutorial](/docs/verification/remote-mobile-verifiers/tutorial) for step-by-step implementation guidance. * Review the SDK reference documentation for your platform: * [iOS](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk) * [Android](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) * [React Native](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest) * Learn more about the [remote verification workflow](/docs/verification/remote-mobile-verifiers/workflow). # Learn how to build an application that can verify an mDoc from another app on the same device URL: /docs/verification/remote-mobile-verifiers/tutorial ## Overview [#overview] In this tutorial you will use the [mDocs Mobile Verifier SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview) to build an application that can verify an [mDoc](/docs/concepts/mdocs) presented from a different application on the same device via [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html), as defined in [ISO 18013-7](https://www.iso.org/standard/91154.html) Annex B. Remote mobile app verification overview 1. A relying party uses the Mobile Verifier SDK to embed a remote verification workflow into a mobile application. 2. When a user interacts with the mobile application, a matching wallet application installed on their mobile device is invoked to request an mDoc for verification. 3. The user consents to sharing the requested information. 4. The user's wallet application shares the matching mDoc with the MATTR VII tenant configured by the Mobile Verifier SDK to perform the verification workflow. 5. The MATTR VII tenant performs the required checks and returns the verification results via the Mobile Verifier SDK to the verifier application. 6. The user journey continues based on the verification results. The result will look something like this: Similar to the iOS and Android videos, but with a single app running on both platforms. To achieve this, you will build the following capabilities into your verifier application: * Initialize the SDK, so that your application can use its functions and classes. * Request an mDoc for verification from a compliant wallet application. * Handle the redirect from the wallet application. * Display the verification results. ## Prerequisites [#prerequisites] Before you get started, let's make sure you have everything you need. ### Prior knowledge [#prior-knowledge] * The remote verification workflow described in this tutorial is based on the [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html) specification and the [ISO 18013-7](https://www.iso.org/standard/91154.html) standard. If you are unfamiliar with these, refer to the following resources for more information: * What are [mDocs](/docs/concepts/mdocs)? * What is [remote verification](/docs/verification/remote-overview)? * Breakdown of the [remote mobile app verification workflow](/docs/verification/remote-mobile-verifiers/workflow). * We assume you have experience developing applications in the relevant programming languages and frameworks (Swift for iOS and Kotlin for Android). ### Assets [#assets] * Use the [Get Started form](/docs/resources/get-started) to request a trial of MATTR **verification** capabilities. You will receive access to the following resources: * MATTR Pi mDocs Verifier SDK for your chosen platform (iOS, Android, or React Native). * MATTR VII tenant. * As part of your MATTR Pi SDK onboarding process you will be provided with access to the following resources: * ZIP file which includes the required framework: (`MobileCredentialVerifierSDK.xcframework.zip`). * [Sample Verifier app](https://github.com/mattrglobal/sample-apps/tree/master/ios-remote-verification-tutorial-sample-app): You can use this app for reference as you work through this tutorial. This tutorial is only meant to be used with the [latest version](/docs/verification/sdks/overview#versions) of the iOS mDocs Verifier SDK. * Use the [Get Started form](/docs/resources/get-started) to request a trial of MATTR **verification** capabilities. You will receive access to the following resources: * MATTR Pi mDocs Verifier SDK for your chosen platform (iOS, Android, or React Native). * MATTR VII tenant. * As part of your MATTR Pi SDK onboarding process you will be provided with access to the following resources: * A ZIP file that includes the required library (`mobile-credential-verifier-*version*.zip`). * [Sample Verifier app](https://github.com/mattrglobal/sample-apps/tree/master/android-remote-verification-tutorial-sample-app): You can use this app for reference as we work through this tutorial. This tutorial is only meant to be used with the [latest version](/docs/verification/sdks/overview#versions) of the Android mDocs Verifier SDK. * Use the [Get Started form](/docs/resources/get-started) to request a trial of MATTR **verification** capabilities. You will receive access to the following resources: * MATTR Pi mDocs Verifier SDK for your chosen platform (iOS, Android, or React Native). * MATTR VII tenant. * Access to the [@mattrglobal/mobile-credential-verifier-react-native](https://www.npmjs.com/package/@mattrglobal/mobile-credential-verifier-react-native) npm package, provided as part of your MATTR Pi SDK onboarding process. * [Sample Verifier app](https://github.com/mattrglobal/sample-apps/tree/master/react-native-remote-verification-tutorial/react-native-remote-verification-tutorial-complete): This tutorial is accompanied by a sample app you can use for reference as you work through it. This tutorial is only meant to be used with the [latest version](/docs/verification/sdks/overview#versions) of the React Native mDocs Verifier SDK. ### Development environment [#development-environment] * [Xcode](https://developer.apple.com/xcode/) setup with either: * Local build settings if you are developing locally. * [iOS developer account](https://developer.apple.com/programs/enroll/) if you intend to publish your app. * [Android Studio](https://developer.android.com/studio). * [Node.js](https://nodejs.org/) (version 18 or later) and a package manager such as [Yarn](https://yarnpkg.com/). * A [React Native development environment](https://reactnative.dev/docs/set-up-your-environment). This tutorial uses [Expo](https://docs.expo.dev/) with development builds. * Platform tooling for the device you intend to target: * [Xcode](https://developer.apple.com/xcode/) for iOS, with either local build settings or an [iOS developer account](https://developer.apple.com/programs/enroll/) if you intend to publish your app. * [Android Studio](https://developer.android.com/studio) for Android. ### Testing device [#testing-device] You will need a mobile device to test the workflow with. Supported iOS device to run the built Verifier application on, setup with: * Available internet connection * A wallet application that can present an mDoc remotely as per ISO/IEC 18013-7 and OID4VP. We recommend using the sample holder app you can build with our [Holder SDK quickstart guide](/docs/holding/sdk-quickstart). * Use your testing wallet application to claim an mDoc by scanning the following QR code: QR code Supported Android device to run the built Verifier application on, setup with: * Available internet connection * A wallet application that can present an mDoc remotely as per ISO/IEC 18013-7 and OID4VP. We recommend using the sample holder app you can build with our [Holder SDK quickstart guide](/docs/holding/sdk-quickstart). * Use your testing wallet application to claim an mDoc by scanning the following QR code: QR code A supported iOS or Android device to run the built Verifier application on, setup with: * Available internet connection * A wallet application that can present an mDoc remotely as per ISO/IEC 18013-7 and OID4VP. We recommend using the sample holder app you can build with our [Holder SDK quickstart guide](/docs/holding/sdk-quickstart). * Use your testing wallet application to claim an mDoc by scanning the following QR code: QR code React Native apps built with Expo development builds must run on a physical device, not a simulator or emulator, to complete the app-to-app presentation flow with a wallet installed on the same device. Got everything? Let's get going! ## Overview [#overview-1] The following diagram depicts the workflow you will build in this tutorial: 1. The user triggers the workflow by interacting with the verifier application. 2. The verifier application uses the embedded Mobile Verifier SDK capabilities to start a presentation-based verification session with the configured MATTR VII tenant. 3. The MATTR VII tenant responds with a link to invoke a matching wallet application. 4. The verifier application uses the link to invoke a matching wallet application using a redirect. 5. The wallet application makes a request to the MATTR VII tenant to retrieve a request object, defining what information is requested for verification. 6. The MATTR VII tenant returns the request object to the wallet application. 7. The wallet application (upon user consent) returns an authorization response to the MATTR VII tenant, which includes the information required for verification. 8. The MATTR VII tenant returns the verification results to the verifier application. 9. The verifier application surfaces the verification results to the user and the interaction continues. You will build this workflow in two parts: 1. [Part 1: Setup the MATTR VII Verifier tenant](#part-1-setup-the-mattr-vii-verifier-tenant). 2. [Part 2: Build a mobile application with mDocs verification capabilities](#part-2-build-a-mobile-application-with-mdocs-verification-capabilities). ## Part 1: Setup the MATTR VII Verifier tenant [#part-1-setup-the-mattr-vii-verifier-tenant] The MATTR VII tenant will be used to interact with your mobile application (generating a verification request) and the wallet application (presenting an mDoc for verification) as per OID4VP and ISO/IEC 18013-7 Annex B. To enable this, you must: 1. [Create a MATTR VII tenant](#create-a-mattr-vii-tenant): This is the tenant that will be used to perform the verification workflow. 2. [Create a verifier application configuration](#create-a-verifier-application-configuration): Define what applications can create verification sessions with the MATTR VII tenant, and how to handle these requests. 3. [Create a supported wallet configuration](#create-a-supported-wallet-configuration): Define how to invoke specific wallet applications as part of a remote verification workflow. 4. [Configure a trusted issuer](#configure-a-trusted-issuer): The MATTR VII verifier tenant will only accept mDocs issued by these trusted issuers. ### Create a MATTR VII tenant [#create-a-mattr-vii-tenant] If you already have a tenant you can skip this step. 1. Log into the [MATTR Portal](https://portal.mattr.global). 2. Select the **Create/switch tenant** button on the top-right side of the screen.\ The *All tenants* panel is displayed, listing any existing tenants. 3. Select the **Create new** button.\ The *New tenant* form is displayed. 4. Use the *Region* dropdown list to select the region your tenant will be hosted in. 5. Use the *Tenant subdomain* text box to insert a subdomain for your tenant (e.g. `remote-mobile-verification`). 6. Select the **Create** button to create the new tenant. 7. Copy the displayed tenant information (`audience`, `auth_url`, `tenant_url`, `client_id` and `client_secret`) which is required for the next step. ### Create a verifier application configuration [#create-a-verifier-application-configuration] The iOS and Android Verifier SDKs must be **tethered** to a MATTR VII tenant. On initialization, the SDK registers your app instance with the tenant and obtains a license, so SDK Tethering must be configured before you initialize the SDK. For a full explanation of tethering and the capabilities it enables, see [SDK Tethering](/docs/verification/sdks/sdk-tethering). For remote mobile (app-to-app) verification, the Verifier Application also defines the OID4VP redirect URI used to return the user to your app after the wallet presents the credential. To enable SDK Tethering and configure the OID4VP redirect URI, create a Verifier Application on your MATTR VII tenant: Make a request of the following structure to create an iOS Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My iOS Mobile Verifier Application", "type": "ios", "bundleId": "com.yourname.mobileverifier", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" }, "openid4vpConfiguration": { "redirectUri": "com.yourname.mobileverifier://my/path" } } ``` * `name` : A unique name to identify this Verifier Application. * `type` : Must be `ios`. * `bundleId` : The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId` : Your Apple Developer Team ID. * `appAttest` : App Attest configuration for the iOS verifier application: * `required` : When `true`, the app instance must provide a valid App Attest attestation during registration and token renewal. When `false`, the app can fall back to assertion-only authentication. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `environment` : The App Attest environment (`development` or `production`). Apple recommends using `development` for testing and `production` for distribution builds. * `openid4vpConfiguration` : * `redirectUri` : The URI the user is redirected to after presenting the credential from their wallet. The example uses the bundle ID as the scheme, but you can use any custom URL scheme. Only the scheme must match the custom URL scheme you register in the app; the path segment is illustrative. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", // [!code focus] "name": "My iOS Mobile Verifier Application", "type": "ios", // ... rest of application configuration } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. Make a request of the following structure to create an Android Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Android Mobile Verifier Application", "type": "android", "packageName": "com.example.mobileverifiertutorial", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false }, "openid4vpConfiguration": { "redirectUri": "com.example.mobileverifiertutorial://oid4vp-callback" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `android`. * `packageName`: The package name of your Android application. Must match the package name you will define in your Android project later. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/verification/android-app-signing) for more information. We will update this value later in the tutorial after you generate the signing key and obtain its thumbprint. * `keyAttestation`: Key Attestation configuration for the Android verifier application: * `required`: When `true`, the app instance must provide a valid Key Attestation during registration and token renewal. When `false`, the app can register and renew tokens using just an authentication assertion. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `openid4vpConfiguration`: * `redirectUri`: The URI the user is redirected to after presenting the credential from their wallet. Structure it as `{your_app_packageName}://oid4vp-callback`. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", // [!code focus] "name": "My Android Mobile Verifier Application", "type": "android", // ... rest of application configuration } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. A React Native app runs on both iOS and Android, and the MATTR VII tenant validates requests differently for each platform. Create a Verifier Application for **each platform you intend to run on** by following the **iOS** and **Android** tabs above. Each platform's `redirectUri` must match that platform's URL scheme, so configure one Verifier Application per platform and use the matching application `id` at runtime. ### Create a supported wallet configuration [#create-a-supported-wallet-configuration] Verifier applications can define specific wallet applications to accept mDocs from as part of their verification workflows. The MATTR VII verifier tenant needs to be configured with a specific URI scheme that will be used to invoke these wallets. 1. Log in to the [MATTR Portal](https://portal.mattr.global/) (if you haven't already). 2. In the navigation panel on the left-hand side, expand the **Credential Verification** menu. 3. Click on **Supported wallets**. 4. Click on **Create new**. 5. Enter a meaningful *Name* for the new supported wallet (e.g. "My Supported Wallet"). 6. Enter `mdoc-openid4vp://` in the *Authorization Endpoint* field. This is the URI scheme that will be used to invoke the wallet application. More information on applying different URI schemes and the resulting user experience can be found in the [workflow page](/docs/verification/remote-mobile-verifiers/workflow#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application). 7. Click on **Create**. The `authorizationEndpoint` configured in the example above (`mdoc-openid4vp://`) is the default OID4VP scheme. While this is technically redundant, we chose to include this step to explain how to configure this endpoint for wallet application using different schemes. More information on applying different URI schemes and the resulting user experience can be found in the [workflow page](/docs/verification/remote-mobile-verifiers/workflow#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application). Make the following request to your MATTR VII tenant to [create a trusted wallet provider configuration](/docs/verification/remote-verification-api-reference/wallet-providers#create-a-wallet-provider): ```http title="Request" POST /v2/presentations/wallet-providers ``` ```json title="Request body" { "name": "My Trusted Wallet Provider", "openid4vpConfiguration": { "authorizationEndpoint": "mdoc-openid4vp://" } } ``` * `name` : Unique name to identify this trusted wallet provider. * `authorizationEndpoint` : URI scheme that will be used to invoke the wallet application. More information on applying different URI schemes and the resulting user experience can be found in the [workflow page](/docs/verification/remote-mobile-verifiers/workflow#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application). The `authorizationEndpoint` configured in the example above (`mdoc-openid4vp://`) is the default OID4VP scheme. While this is technically redundant, we chose to include this step to explain how to configure this endpoint for wallet application using different schemes. *Response* ```json title="Response body" { "id": "99890c34-e4b7-4a23-84d6-e5de57114c00", // [!code focus] "name": "My Trusted Wallet Provider", "openid4vpConfiguration": { "authorizationEndpoint": "mdoc-openid4vp://" } } ``` * `id` : You will use this value later to indicate this is the wallet the verifier application expects to receive mDocs from. ### Configure a trusted issuer [#configure-a-trusted-issuer] 1. In the navigation panel on the left-hand side, expand the **Credential Verification** menu. 2. Click on **Trusted issuers**. 3. Click on **Create new**. 4. Copy and paste the following certificate in the *Certificate PEM file* field: ``` -----BEGIN CERTIFICATE----- MIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG EwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew HhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp MCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp dB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq 232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu bWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp ZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp bGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny bDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI SNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6 -----END CERTIFICATE----- ``` 6. Click on **Add**. You must configure trusted issuers on your MATTR VII verifier tenant, as presented mDocs will only be verified if they had been issued by a trusted issuer. This is achieved by providing the PEM certificate of the IACA used by these issuers to sign mDocs. In production environments the issuer can provide it out of band or you can obtain it via their [issuer's metadata](/docs/verification/remote-mobile-verifiers/tutorial#configure-a-trusted-issuer). Make the following request to your MATTR VII tenant to [configure a truster issuer](/docs/verification/remote-verification-api-reference/trusted-issuers#create-a-trusted-issuer): ```http title="Request" POST /v2/credentials/mobile/trusted-issuers ``` ```json title="Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\nMIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG\nEwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew\nHhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp\nMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq\nhkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp\ndB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud\nEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq\n232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu\nbWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp\nZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp\nbGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny\nbDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI\nSNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6\n-----END CERTIFICATE-----" } ``` * `certificatePem` : This is the IACA identifying the [MATTR Labs demo issuer](https://montcliff-dmv.mattrlabs.com/dashboard) which issues the credential referenced in the [Testing device prerequisite](#testing-device). If you intend to test this tutorial with a credential different than the one recommended in our [Testing device prerequisite](#testing-device), replace the `certificatePem` value with your own issuer's IACA. A successful `201` response indicates that this issuer's certificate was added to your MATTR VII tenant's trusted issuer's list. This means that mDocs that use this IACA as their root certificate can be trusted and verified. ## Part 2: Build a mobile application with mDocs verification capabilities [#part-2-build-a-mobile-application-with-mdocs-verification-capabilities] Now that the MATTR VII verifier tenant is properly configured, you can proceed with the steps required to embed verification capabilities into your mobile verifier application: 1. [Setup your environment](#step-1-environment-setup): Setup the required infrastructure for your mobile application. 2. [Initialize the SDK](#step-2-initialize-the-sdk): So that the SDK functions are available in your mobile application. 3. [Request a credential from wallet application](#step-3-request-a-credential-from-wallet-application): Build the capability to request an mDoc for verification from a wallet application. 4. [Display verification results](#step-4-display-verification-results): ### Step 1: Environment setup [#step-1-environment-setup] **Step 1: Create a new project** Follow the detailed instructions to [Create a new Xcode Project](https://help.apple.com/xcode/mac/current/#/dev07db0e578) and add your organization's identifier. **Step 2: Unzip the dependencies file** 1. Unzip the [`MobileCredentialVerifierSDK.xcframework.zip` file](#assets). 2. Drag the `MobileCredentialVerifierSDK.xcframework` folder into your project. 3. Configure `MobileCredentialVerifierSDK.xcframework` to [Embed & sign](https://help.apple.com/xcode/mac/current/#/dev51a648b07). See [Add existing files and folders](https://help.apple.com/xcode/mac/current/#/dev81ce1d383) for detailed instructions. This should result in the the following framework being added to your project: Framework added **Step 3: Run the application** Select **Run** and make sure the application launches with a *“Hello, world!”* text in the middle of the display, as shown in the following image: Application ready **Step 1: Create a new project** 1. [Create a new Android Studio project](https://developer.android.com/studio/projects/create-project), using the *Empty Activity* template. Create a new Android project 2. Name the project `Mobile Verifier Tutorial`. 3. Set the `Package name` to `com.example.mobileverifiertutorial` (the value you used for the `packageName` when you [created the verifier application](#create-a-verifier-application-configuration) on your MATTR VII tenant). 4. Select *API 24* as the `Minimum SDK` version. 5. Select *Kotlin DSL* as the `Build configuration language`. Project configuration 5. Click **Finish**. 6. [Sync](https://developer.android.com/build#sync-files) the project with Gradle files. **Step 2: Add required dependencies** 1. Select the [Project view](https://developer.android.com/studio/projects#ProjectView). 2. Create a new directory named `repo` in your project's folder. 3. Unzip the `mobile-credential-verifier-*version*.zip` file and copy the unzipped `global` folder into the new `repo` folder. 4. Open the `settings.gradle.kts` file in the `MobileVerifierTutorial` folder and add the following Maven repository to the `dependencyResolutionManagement.repositories` block: ```kotlin title="settings.gradle.kts" maven { url = uri("repo") } ``` 5. Open the `app/build.gradle.kts` file in your app folder and add the following dependencies to the `dependencies` block: ```kotlin title="app/build.gradle.kts" implementation("global.mattr.mobilecredential:verifier:7.0.0") implementation("androidx.navigation:navigation-compose:2.9.0") ``` * The `verifier` dependency version should match the version of the unzipped `mobile-credential-verifier-*version*.zip` file you copied to the `repo` folder. * The required `navigation-compose` version may differ based on your version of the IDE, Gradle, and other project dependencies. 6. Open the `gradle/libs.versions.toml` file and pin the following library versions: ```kotlin title="gradle/libs.versions.toml" coreKtx = "1.18.0" lifecycleRuntimeKtx = "2.10.0" ``` Newly scaffolded projects can pull in transitive AndroidX libraries (such as `androidx.core:core-ktx` and `androidx.lifecycle:*`) whose latest versions require a newer `compileSdk` and Android Gradle plugin than Android Studio scaffolded, which causes AAR metadata check failures on sync. Pinning these versions keeps them compatible with the scaffolded toolchain. 7. [Sync](https://developer.android.com/build#sync-files) the project with Gradle files. 8. Open the [Build](https://developer.android.com/studio/run#gradle-console) tab and select `Sync` to make sure that the project has synced successfully. Synced successfully **Step 3: Run the application** 1. Connect a [debuggable](https://developer.android.com/studio/debug/dev-options#Enable-debugging) mobile device to your machine. 2. [Build and run the app](https://developer.android.com/studio/run) on the connected mobile device. The app should launch with a *“Hello, Android!”* text displayed. Blank app **Step 4: Update app signing certificate** 1. Retrieve your app's signing certificate thumbprint and convert it to the required format: * Open a terminal inside your project's root folder and run: ```bash title="Retrieve signing certificate" ./gradlew signingReport ``` If you see `permission denied: ./gradlew`, the Gradle wrapper has lost its executable bit (this happens when the project is downloaded as a zip rather than cloned). Make it executable and run the command again: ```bash title="Fix wrapper permissions" chmod +x gradlew ``` * Locate the `SHA-256` value in the `Variant: debug` section, remove all colons (`:`), and convert all letters to lowercase. The result is the thumbprint you will send to your tenant in the next step: ```bash title="Example conversion" echo '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5' | tr -d ':' | tr 'A-F' 'a-f' ``` This thumbprint is only valid for your debug builds. If you intend to publish your app, repeat this process with the release signing certificate. Refer to [Android App Signing](/docs/verification/android-app-signing) for more information. 2. Update your verifier application with the formatted thumbprint by making a request of the following structure. Use the application `id` returned in [Create a verifier application configuration](#create-a-verifier-application-configuration) as the `{applicationId}` path parameter, and set `packageSigningCertificateThumbprints` to the thumbprint you generated in the previous step: ```http title="Request" PUT /v2/presentations/applications/{applicationId} ``` ```json title="Request body" { "name": "My Android Mobile Verifier Application", "type": "android", "packageName": "com.example.mobileverifiertutorial", "packageSigningCertificateThumbprints": [ "91f7cbf9d681531bc7a58fb833cca14dabede509c5" ], "openid4vpConfiguration": { "redirectUri": "com.example.mobileverifiertutorial://oid4vp-callback" } } ``` The `PUT` request replaces the entire application configuration, so include all the fields you set when you created the application in [Create a verifier application configuration](#create-a-verifier-application-configuration), not just the thumbprint. A successful response returns a `200` status code with the updated Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", "name": "My Android Mobile Verifier Application", "type": "android", "packageSigningCertificateThumbprints": [ "91f7cbf9d681531bc7a58fb833cca14dabede509c5" ], // ... rest of application configuration } ``` **Step 1: Access the tutorial starter codebase** 1. Access the tutorial starter codebase by either: * Cloning the MATTR sample-apps repository: ```bash title="Clone the repository" git clone https://github.com/mattrglobal/sample-apps.git ``` * Or downloading just the starter directory using the [download-directory.github.io](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fmattrglobal%2Fsample-apps%2Ftree%2Fmaster%2Freact-native-remote-verification-tutorial%2Freact-native-remote-verification-tutorial-starter) utility. 2. Open the tutorial project in your code editor. You can find it in the `sample-apps/react-native-remote-verification-tutorial/react-native-remote-verification-tutorial-starter/` directory. You can find the completed tutorial code in the `sample-apps/react-native-remote-verification-tutorial/react-native-remote-verification-tutorial-complete` directory and use it as a reference as you work through this tutorial. **Step 2: Configure the app identifiers** 1. Open the `app.config.ts` file and update the `bundleIdentifier` value under the `// Update the bundle identifier` comment to the iOS bundle ID you used when you [created the verifier application](#create-a-verifier-application-configuration): ```ts title="app.config.ts" bundleIdentifier: "global.mattr.learn.rnremoteverifiersampleapp", ``` 2. Update the `package` value under the `// Update the package name` comment to the Android package name you used when you created the verifier application: ```ts title="app.config.ts" package: "global.mattr.learn.rnremoteverifiersampleapp", ``` 3. Add the custom URL scheme under the `// Add the custom URL scheme used for the wallet redirect` comment. This registers the scheme the wallet uses to redirect back to your app. Set it to your **iOS bundle identifier**: ```ts title="app.config.ts" scheme: "global.mattr.learn.rnremoteverifiersampleapp", ``` **Step 3: Configure the app plugins** Add the following code under the `// Configure the app plugins` comment to register the required plugin configurations: ```ts title="app.config.ts" "./withMobileCredentialAndroidVerifierSdk", "./withOpenid4VpCallbackActivity", [ "expo-build-properties", { android: { minSdkVersion: 24, compileSdkVersion: 36, targetSdkVersion: 34, kotlinVersion: "2.0.21", }, }, ], ``` These plugin files have already been created in your project root directory: * `withMobileCredentialAndroidVerifierSdk` adds the Maven repository that hosts the native Android Verifier SDK. * `withOpenid4VpCallbackActivity` declares the SDK's OpenID4VP callback activity in `AndroidManifest.xml`, so the wallet can redirect back to your app on Android. The activity is bound to `{your_packageName}://oid4vp-callback`. You can also follow the instructions in the [mDocs Verifier](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:platform-android) SDK Docs to perform this platform-specific configuration manually. **Step 4: Install the dependencies** 1. Open a terminal in the project's root and navigate to the starter project directory: ```bash title="Navigate to the project directory" cd sample-apps/react-native-remote-verification-tutorial/react-native-remote-verification-tutorial-starter/ ``` 2. Install the application dependencies: ```bash title="Install dependencies" yarn install ``` **Step 5: Generate the iOS and Android project files** Run the following command to generate the native project files: ```bash title="Generate project files" yarn expo prebuild ``` You should now see the `ios` and `android` folders in your project root. **Step 6: Start the application** Connect your testing device and run the following command for your target platform: ```bash title="Run iOS application" yarn ios --device ``` ```bash title="Run Android application" yarn android --device ``` The app should launch with a single **Request credentials** button. **Step 7 (Android only): Update the app signing certificate** 1. From your project root, change into the generated `android` directory and retrieve your application's signing certificate: ```bash title="Retrieve signing certificate" cd android && ./gradlew signingReport ``` 2. Locate the `SHA-256` value in the `Variant: debug` section, remove all colons (`:`), and convert all letters to lowercase: ```bash title="Example conversion" echo '91:F7:CB:F9:D6:81:53:1B:C7:A5:8F:B8:33:CC:A1:4D:AB:ED:E5:09:C5' | tr -d ':' | tr 'A-F' 'a-f' ``` 3. Update your Android verifier application with this thumbprint by making a [`PUT /v2/presentations/applications/{applicationId}`](/docs/verification/remote-verification-api-reference/verifier-applications#update-a-verifier-application) request to your MATTR VII tenant, setting `packageSigningCertificateThumbprints` to the value you just generated (use the application `id` you created in [Part 1](#create-a-verifier-application-configuration)). This thumbprint is only valid for your debug builds. If you intend to publish your app, repeat this process with the release signing certificate. Refer to [Android App Signing](/docs/verification/android-app-signing) for more information. ### Step 2: Initialize the SDK [#step-2-initialize-the-sdk] The first capability you will build into your app is to initialize the SDK so that the app can use its functions and classes. To achieve this, you need to import the `MobilecredentialVerifierSDK` framework and then initialize the `MobileCredentialVerifier` class. 1. Open the `ContentView` file in your new project and replace any existing code with the following: ```swift title="ContentView" import SwiftUI // Step 2.3: Import MobileCredentialVerifierSDK struct ContentView: View { @State var viewModel: VerifierViewModel = VerifierViewModel() var body: some View { NavigationStack(path: $viewModel.navigationPath) { VStack { Button("Request credentials") { viewModel.requestCredentials() } .padding() } .navigationDestination(for: NavigationState.self) { destination in switch destination { case .viewResponse: presentationResponseView } } } // Step 4.2: Handle MATTR VII redirect } // MARK: Verification Views var presentationResponseView: some View { // Step 4.4: Create PresentationResponseView EmptyView() } } // MARK: VerifierViewModel @Observable final class VerifierViewModel { var navigationPath = NavigationPath() // Step 2.4: Setup platform configuration // Step 2.5: Add MobileCredentialVerifier var // Step 2.6: Initialize the SDK // Step 3.1: Create MobileCredentialRequest instance // Step 3.2: Create receivedDocuments variable } // MARK: Same Device Verification extension VerifierViewModel { func requestCredentials() { // Step 3.3: Request credentials } } // MARK: - Navigation enum NavigationState: Hashable { case viewResponse } ``` This will serve as the basic structure for your application. You will copy and paste different code snippets into specific locations to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step. We recommend copying and pasting the comment text in Xcode search field (e.g. `Step 2.3: Import MobileCredentialVerifierSDK`) to easily locate it in the code. 2. Create a new file named `Constants` and paste the following code into it to define constants which are required to initialize the SDK: ```swift title="Constants" import Foundation enum Constants { static let tenantHost = URL(string: "https://learn.vii.au01.mattr.global")! static let applicationID = "74f91a0f-5909-43b0-a431-6da2397d1f86" } ``` * `tenantHost` : Replace with the URL of your [MATTR VII tenant](#part-1-setup-the-mattr-vii-verifier-tenant). * `applicationID` : Replace with the `id` returned when you created the MATTR VII [verifier application](#create-a-verifier-application-configuration). 3. Return to the `ContentView` file and add the following code after the `Step 2.3: Import MobileCredentialVerifierSDK` comment to import `MobileCredentialVerifierSDK` and gain access to the SDK capabilities: ```swift title="ContentView" import MobileCredentialVerifierSDK ``` 4. Add the following code under the `Step 2.4: Setup platform configuration` comment to create a [`PlatformConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/platformconfiguration) instance: ```swift title="ContentView" let platformConfiguration = PlatformConfiguration( tenantHost: Constants.tenantHost, applicationId: Constants.applicationID ) ``` This instance configures the MATTR VII tenant that the SDK will interact with (`tenantHost`) and the Verifier Application the SDK represents (`applicationId`), based on the constants defined in the `Constants` file. From v6.0.0, the `applicationId` is supplied here via `PlatformConfiguration` (it is no longer passed to `requestMobileCredentials`), and it also drives [SDK Tethering](/docs/verification/sdks/sdk-tethering). 5. Add the following code after the `Step 2.5: Add MobileCredentialVerifier var` comment to create a variable that will hold the `mobileCredentialVerifier` instance when the SDK is initialized: ```swift title="ContentView" var mobileCredentialVerifier: MobileCredentialVerifier ``` 6. Add the following code after the `Step 2.6: Initialize the SDK` comment to initialize the `MobileCredentialVerifier` instance with the parameters defined in the `platformConfiguration` instance: ```swift title="ContentView" init() { mobileCredentialVerifier = MobileCredentialVerifier.shared Task { do { try await mobileCredentialVerifier.initialize(platformConfiguration: platformConfiguration) } catch MobileCredentialVerifierError.failedToRegister { // Registration with the MATTR VII tenant failed during SDK Tethering. // Print the reason so the failure is visible: check connectivity and that // tenantHost, applicationId, and the app's bundle identifier / team ID match // the Verifier Application configuration. print("SDK initialization failed to register:", MobileCredentialVerifierError.failedToRegister) } catch MobileCredentialVerifierError.invalidLicense { // The SDK license is missing, invalid, or expired. print("SDK initialization failed: invalid license") } catch { print("SDK initialization failed:", error.localizedDescription) } } } ``` The `initialize` method is asynchronous (called here from a `Task`) and drives SDK Tethering: on first launch it registers the app instance with your tenant and obtains a license. Network access is required the first time the SDK initializes and when the license is later renewed. 7. [Run](https://developer.apple.com/documentation/xcode/running-your-app-in-simulator-or-on-a-device) the app to ensure it compiles successfully. The first capability you will build into your app is to initialize the SDK so that the app can use its functions and classes. To achieve this, you need to import the `MobilecredentialVerifierSDK` framework and then initialize the `MobileCredentialVerifier` class. 1. Open the `MainActivity` file in your new project and replace any existing code with the following: ```kotlin title="MainActivity" package com.example.mobileverifiertutorial import android.app.Activity import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Button import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.ViewModel import androidx.lifecycle.lifecycleScope import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel import global.mattr.mobilecredential.verifier.deviceretrieval.devicerequest.DataElements import global.mattr.mobilecredential.verifier.deviceretrieval.devicerequest.NameSpaces import global.mattr.mobilecredential.verifier.dto.MobileCredentialPresentation import global.mattr.mobilecredential.verifier.dto.MobileCredentialRequest import global.mattr.mobilecredential.verifier.MobileCredentialVerifier import global.mattr.mobilecredential.verifier.OnlinePresentationSessionResult import global.mattr.mobilecredential.verifier.platformconfig.PlatformConfiguration import global.mattr.mobilecredential.verifier.exception.VerifierException.FailedToRegisterException import global.mattr.mobilecredential.verifier.exception.VerifierException.InvalidLicenseException import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import java.net.URL class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { MobileVerifierTutorialTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Content( modifier = Modifier.padding(innerPadding) ) } } } // Step 2.3: Setup platform configuration // Step 2.4: Initialize the SDK } } @Composable fun Content(modifier: Modifier = Modifier) { val activity = (LocalContext.current) as Activity val viewModel: VerifierViewModel = viewModel() val documents by viewModel.receivedDocuments.collectAsState() Column( modifier = modifier .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Button(onClick = { viewModel.requestCredentials(activity) }) { Text("Request credentials") } LazyColumn(modifier = Modifier.fillMaxWidth()) { items(documents) { document -> // Step 4.5: Display received documents } } } } @Preview(showBackground = true) @Composable fun ContentPreview() { MobileVerifierTutorialTheme { Content() } } class VerifierViewModel : ViewModel() { private val _receivedDocuments = MutableStateFlow>(emptyList()) val receivedDocuments: StateFlow> = _receivedDocuments fun requestCredentials(activity: Activity) { // Step 3.1: Create MobileCredentialRequest instance viewModelScope.launch { _receivedDocuments.value = emptyList() try { // Step 3.2: Request credentials // Step 4.2: Handle response } catch (e: Exception) { e.printStackTrace() } } } } ``` This will serve as the basic structure for your application. You will copy and paste different code snippets into specific locations to achieve the different functionalities. These locations are indicated by comments that reference both the section and the step. We recommend copying and pasting the comment text in the Android Studio search field (e.g. `Step 2.3: Setup platform configuration`) to easily locate it in the code. 2. Create a new file named `Constants` and paste the following code, **replacing the values as indicated**, to define constants which are required to initialize the SDK: ```kotlin title="Constants" package com.example.mobileverifiertutorial object Constants { const val TENANT_HOST = "https://learn.vii.au01.mattr.global" const val APPLICATION_ID = "74f91a0f-5909-43b0-a431-6da2397d1f86" } ``` * `TENANT_HOST` : Replace with the URL of your [MATTR VII tenant](#part-1-setup-the-mattr-vii-verifier-tenant). This indicates the tenant that the SDK will interact with. * `APPLICATION_ID` : Replace with the `id` returned when you created the MATTR VII [verifier application](#create-a-verifier-application-configuration). This indicates the verifier application that the SDK will represent. 3. Add the following code under the `Step 2.3: Setup platform configuration` comment to create a [`PlatformConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier.platformconfig/-platform-configuration/index.html) instance: ```kotlin title="MainActivity" val platformConfiguration = PlatformConfiguration( tenantHost = URL(Constants.TENANT_HOST), applicationId = Constants.APPLICATION_ID ) ``` This instance configures the MATTR VII tenant that the SDK will interact with (`tenantHost`) and the Verifier Application the SDK represents (`applicationId`), based on the constants defined in the `Constants` file. 4. Add the following code after the `Step 2.4: Initialize the SDK` comment to initialize the `MobileCredentialVerifier` instance with the parameters defined in the `platformConfiguration` instance: ```kotlin title="MainActivity" lifecycleScope.launch { try { MobileCredentialVerifier.initialize( context = this@MainActivity, platformConfiguration = platformConfiguration ) } catch (e: FailedToRegisterException) { // Registration with the MATTR VII tenant failed during SDK Tethering. // Log the reason so the failure is visible: check connectivity and that // tenantHost, applicationId, and the app's package name / signing certificate // thumbprint match the Verifier Application configuration. Log.e("VerifierTutorial", "SDK initialization failed to register", e) } catch (e: InvalidLicenseException) { // The SDK license is missing, invalid, or expired. Log.e("VerifierTutorial", "SDK initialization failed: invalid license", e) } } ``` 5. [Run](https://developer.android.com/studio/run) the app to ensure it compiles successfully. The first capability you will build into your app is to initialize the SDK so that the app can use its functions and classes. To achieve this, you import the SDK and call `initialize` with your tenant host configuration. 1. Open the `App.tsx` file in your project and replace the existing code with the following skeleton structure: ```tsx title="App.tsx" import { type MobileCredentialResponse, handleDeepLink, initialize, requestMobileCredentials, } from "@mattrglobal/mobile-credential-verifier-react-native"; // import { VerificationResultsModal } from "./VerificationResultsModal"; import * as Crypto from "expo-crypto"; import { StatusBar } from "expo-status-bar"; import { useEffect, useState } from "react"; import { ActivityIndicator, Alert, Linking, Platform, SafeAreaView, Text, TouchableOpacity, View } from "react-native"; import { Constants } from "./Constants"; import { styles } from "./styles"; export default function App() { // State variables for SDK initialization, UI, and loading messages. const [isSDKInitialized, setIsSDKInitialized] = useState(false); const [loadingMessage, setLoadingMessage] = useState(false); const [verificationResults, setVerificationResults] = useState(null); const [showVerificationResults, setShowVerificationResults] = useState(false); // Step 2: Initialize the SDK // Step 4.1: Handle the wallet redirect (iOS) // Step 3: Request credentials return ( mDocs Remote Verifier {loadingMessage ? ( {loadingMessage} ) : ( {/* Step 3: Add the Request credentials button */} {!isSDKInitialized && SDK not initialized. Please restart the app.} )} {/* Step 4.3: Display the verification results */} ); } ``` This will serve as the basic structure for your application. You will add code to specific locations to achieve the different functionalities. These locations are indicated by comments that reference the step. We recommend using your editor's search functionality to locate comments like `// Step 2: Initialize the SDK` when adding new code. 2. Create a new file named `Constants.ts` and paste the following code, **replacing the values as indicated**, to define the constants required to initialize the SDK and request credentials: ```ts title="Constants.ts" import { Platform } from "react-native"; /** * Configuration values used to initialize the SDK and request credentials. * * Replace these placeholders with your own values before running the app: * - `TENANT_HOST`: the URL of your MATTR VII tenant, available in the MATTR Portal under * Platform Management > Tenant. * - `IOS_APPLICATION_ID` / `ANDROID_APPLICATION_ID`: the `id` returned when you created the verifier * application configuration on your MATTR VII tenant (see Part 1). * iOS and Android each use their own verifier application, because the redirect URI registered on * the application must match that platform's URL scheme (see `app.config.ts`). Configure one * application ID per platform. */ const IOS_APPLICATION_ID = "your-ios-application-id"; const ANDROID_APPLICATION_ID = "your-android-application-id"; export const Constants = { TENANT_HOST: "https://your-tenant.vii.mattr.global", // Resolves to the verifier application ID for the current platform. APPLICATION_ID: Platform.OS === "ios" ? IOS_APPLICATION_ID : ANDROID_APPLICATION_ID, }; ``` * `TENANT_HOST` : Replace with the URL of your [MATTR VII tenant](#part-1-setup-the-mattr-vii-verifier-tenant). This indicates the tenant that the SDK will interact with. * `IOS_APPLICATION_ID` and `ANDROID_APPLICATION_ID` : Create one verifier application per platform you support, each with a redirect URI that matches that platform's URL scheme. You configure these schemes in `app.config.ts` in [Step 1: Environment setup](#step-1-environment-setup): iOS uses `{scheme}://my/path` and Android uses `{package}://oid4vp-callback`. Because the redirect URI on each verifier application must match the scheme its platform uses, each platform needs its own application. Paste the iOS application `id` into `IOS_APPLICATION_ID` and the Android application `id` into `ANDROID_APPLICATION_ID`. Each `id` is the value returned when you created that platform's verifier application in [Part 1](#part-1-setup-the-mattr-vii-verifier-tenant). * `APPLICATION_ID` : Resolves automatically to the verifier application `id` for the current platform at runtime. The SDK reads this value when requesting credentials, so you do not need to change anywhere it is consumed. 3. Return to the `App.tsx` file and add the following code under the `// Step 2: Initialize the SDK` comment to initialize the SDK with your tenant host: ```tsx title="App.tsx" useEffect(() => { const initializeSDK = async () => { try { setLoadingMessage("Initializing SDK..."); const result = await initialize({ platformConfiguration: { tenantHost: Constants.TENANT_HOST }, }); if (result.isErr()) { console.error("Failed to initialize SDK:", result.error); Alert.alert("Error", "Failed to initialize the verifier SDK"); return; } setIsSDKInitialized(true); } catch (error) { console.error("Failed to initialize SDK:", error); Alert.alert("Error", "Failed to initialize the verifier SDK"); } finally { setLoadingMessage(false); } }; initializeSDK(); }, []); ``` Unlike the in-person (proximity) flow, the remote flow requires a `tenantHost`: the SDK starts presentation sessions with this MATTR VII tenant, which performs the verification server-side and returns the results. The SDK uses a [`Result`](https://github.com/supermacro/neverthrow) type for expected errors, so you check `result.isErr()` rather than relying on a thrown exception. 4. [Run](https://docs.expo.dev/develop/development-builds/use-development-builds/) the app to ensure it compiles successfully. ### Step 3: Request a credential from wallet application [#step-3-request-a-credential-from-wallet-application] Once the SDK is initialized, you can start building the capabilities to request an mDoc for verification from a wallet application. This is done by: 1. Creating a request object that defines what information is required for verification. 2. Sending this request to the wallet application installed on the device. 3. Redirecting the user to the wallet application to present the requested mDoc. 1) Open the `ContentView` file and add the following code under the `Step 3.1: Create MobileCredentialRequest instance` comment to create a new [MobileCredentialRequest](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialrequest) instance: ```swift title="ContentView" let mobileCredentialRequest = MobileCredentialRequest( docType: "org.iso.18013.5.1.mDL", namespaces: [ "org.iso.18013.5.1": [ "family_name": false, "given_name": false, "birth_date": false ] ] ) ``` This object defines what information is required for verification: * The requested credential type (e.g. `org.iso.18013.5.1.mDL`). * The claims required for verification (e.g. `family_name`). * The requested namespace (e.g. `org.iso.18013.5.1`). * Whether or not the verifier intends to persist the claim value (`true`/`false`). Declarative only and not currently enforced by the SDK. For the verification to be successful, the presented credential must include the referenced claim against the specific namespace defined in the request. Our example requests the `birth_date` under the `org.iso.18013.5.1` namespace. If a wallet responds to this request with a credential that includes a `birth_date` but rather under the `org.iso.18013.5.1.US` namespace, the claim will not be verified. 2) Add the following code under the `Step 3.2: Create receivedDocuments variable` comment to create a new `receivedDocuments` variable that will hold the response received from the wallet application: ```swift title="ContentView" var receivedDocuments: [MobileCredentialPresentation] = [] ``` 3) Add the following code under the `Step 3.3: Request credentials` comment to call the SDK's [`requestMobileCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialverifier/requestmobilecredentials\(request:challenge:walletproviderid:\)) method: ```swift title="ContentView" Task { @MainActor in // Clean the response before fetching a new one receivedDocuments = [] do { let onlinePresentationResult = try await mobileCredentialVerifier.requestMobileCredentials(request: [mobileCredentialRequest]) // From v6.0.0, OnlinePresentationSessionResult is a @frozen enum with // success and failure cases. switch onlinePresentationResult { case .success(_, _, let mobileCredentialResponse): receivedDocuments = mobileCredentialResponse?.credentials ?? [] case .failure(_, _, let error): print("No response received: \(error.message)") return } } catch { print(error.localizedDescription) } } ``` The following parameter is passed to the `requestMobileCredentials` method: * `request` : Defines what information to request. This example is passing the `mobileCredentialRequest` instance you created in the previous step. From v6.0.0, the `applicationId` is no longer passed here — it is supplied via `PlatformConfiguration` when you [initialize the SDK](#step-2-initialize-the-sdk). The `challenge` parameter is now optional: when omitted, the SDK generates a cryptographically secure challenge for you. You can still pass your own `challenge` (a unique, unpredictable value per session) to mitigate replay attacks if you prefer to manage it yourself. 4) Run the app and press `Request credentials` button. You will be redirected to a compliant wallet application, where you will see the verification request details and choose what mDoc to present for verification. Once you send the response from the wallet nothing will happen, which is expected at this stage. In the next step you will build the capability to redirect the user back to the verifier application and handle the response from the wallet. 1. Open the `MainActivity` file and add the following code under the `Step 3.1: Create MobileCredentialRequest instance` comment to create a new [MobileCredentialRequest](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier.dto/-mobile-credential-request/index.html?query=data%20class%20MobileCredentialRequest\(val%20docType:%20DocType,%20val%20namespaces:%20NameSpaces\)) instance: ```kotlin title="MainActivity" val mobileCredentialRequest = MobileCredentialRequest( docType = "org.iso.18013.5.1.mDL", namespaces = NameSpaces( mapOf( "org.iso.18013.5.1" to DataElements( mapOf( "family_name" to false, "given_name" to false, "birth_date" to false ) ) ) ) ) ``` This object defines what information is required for verification: * The requested credential type (e.g. `org.iso.18013.5.1.mDL`). * The claims required for verification (e.g. `family_name`). * The requested namespace (e.g. `org.iso.18013.5.1`). * Whether or not the verifier intends to persist the claim value (`true`/`false`). Declarative only and not currently enforced by the SDK. For the verification to be successful, the presented credential must include the referenced claim against the specific namespace defined in the request. Our example requests the `birth_date` under the `org.iso.18013.5.1` namespace. If a wallet responds to this request with a credential that includes a `birth_date` but rather under the `org.iso.18013.5.1.US` namespace, the claim will not be verified. 2. Add the following code under the `Step 3.2: Request credentials` comment to call the SDK's [`requestMobileCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-mobile-credential-verifier/request-mobile-credentials.html) method: ```kotlin title="MainActivity" val onlinePresentationResult = MobileCredentialVerifier.requestMobileCredentials( activity = activity, request = listOf(mobileCredentialRequest) ) ``` The following parameters are passed to the `requestMobileCredentials` method: * `activity` : Defines the current activity context. * `request` : Defines what information to request. This example is passing the `mobileCredentialRequest` instance you created in the previous step. From v7.0.0, `requestMobileCredentials` no longer takes an `applicationId` argument — the SDK uses the `applicationId` you supplied via `PlatformConfiguration` when you [initialized the SDK](#step-2-initialize-the-sdk). 3. Run the app and press `Request credentials` button. You will be redirected to a compliant wallet application, where you will see the verification request details and choose what mDoc to present for verification. Once you send the response from the wallet nothing will happen, which is expected at this stage. In the next step you will build the capability to redirect the user back to the verifier application and handle the response from the wallet. 1. Open the `App.tsx` file and add the following code under the `// Step 3: Request credentials` comment to create a function that builds a request and starts a remote presentation session: ```tsx title="App.tsx" const requestCredentials = async () => { try { setVerificationResults(null); setLoadingMessage("Requesting credentials..."); // Define what information to request: // - docType: the requested credential type (org.iso.18013.5.1.mDL) // - namespaces: the requested namespace (org.iso.18013.5.1) and claims // - Each claim value (false) indicates the verifier does NOT intend to retain the data. const mobileCredentialRequest = { docType: "org.iso.18013.5.1.mDL", namespaces: { "org.iso.18013.5.1": { family_name: false, given_name: false, birth_date: false, }, }, }; const result = await requestMobileCredentials({ request: [mobileCredentialRequest], applicationId: Constants.APPLICATION_ID, challenge: Crypto.randomUUID(), }); if (result.isErr()) { throw new Error(`Failed to request credentials: ${result.error.message}`); } const session = result.value; if (!session.isSuccess) { throw new Error(`Verification session failed: ${session.error.message}`); } const response = session.mobileCredentialResponse; if (!response) { throw new Error("No verification results were returned by the tenant."); } setVerificationResults(response); setShowVerificationResults(true); } catch (error) { console.error("Error requesting credentials:", error); Alert.alert("Error", error instanceof Error ? error.message : String(error)); } finally { setLoadingMessage(false); } }; ``` The following parameters are passed to the [`requestMobileCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/functions/requestMobileCredentials.html) method: * `request` : Defines what information to request. This example passes the `mobileCredentialRequest` object you defined above. For the verification to be successful, the presented credential must include the referenced claims against the specific namespace defined in the request. * `applicationId` : Identifier of the verifier application that will be used to verify the request. In this example it is retrieved from the `Constants` file. * `challenge` : A unique, unpredictable value generated for each verification session to mitigate replay attacks. This example uses `Crypto.randomUUID()` from `expo-crypto`. Always generate a new challenge for every request. 2. Add the following code under the `{/* Step 3: Add the Request credentials button */}` comment to add a button that invokes the `requestCredentials` function: ```tsx title="App.tsx" Request credentials ``` 3. Run the app and press the `Request credentials` button. You will be redirected to a compliant wallet application, where you will see the verification request details and choose what mDoc to present for verification. Once you send the response from the wallet nothing will happen, which is expected at this stage. In the next step you will build the capability to handle the response from the wallet and display the verification results. ### Step 4: Display verification results [#step-4-display-verification-results] Once the user provides their consent to share the requested information, the wallet application will send the response back to the MATTR VII tenant, which will then return the verification results to your verifier application and redirect the user back to the configured redirect URI. In this part of the tutorial you will build the capability to handle this redirect and display the verification results in your application. To enable the redirect back to your verifier application you must register a redirection link. This could be either a [Universal link](https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content/) or a [custom URL scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app). In this tutorial you will use a custom URL scheme. 1. Register a custom URL scheme in your verifier application: * Open the project view and select your application target. * Select the *Info* tab. * Scroll down and expand the URL Types area. * Select the plus button. * Insert your app bundle identifier (as set when you [configured the MATTR VII verifier application](#create-a-verifier-application-configuration)) in both the *Identifier* and *URL Schemes* fields. Custom URL registration 2. In your `ContentView` file, add the following code under the `Step 4.2: Handle MATTR VII redirect` comment to handle the redirect from the wallet application: ```swift title="ContentView" .onOpenURL { url in // Navigate to response screen viewModel.navigationPath.append(NavigationState.viewResponse) viewModel.mobileCredentialVerifier.handleDeepLink(url) } ``` This will pass the redirect URL to the SDK's [`handleDeepLink`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialverifier/handledeeplink\(_:\)) method, which will process the response from the wallet application and update the `receivedDocuments` variable with the verification results. 3. Create a new file named `DocumentView` and add the following code to display the retrieved verification results: ```swift title="DocumentView" import MobileCredentialVerifierSDK import SwiftUI struct DocumentView: View { var viewModel: DocumentViewModel var body: some View { VStack(alignment: .leading, spacing: 10) { Text(viewModel.docType) .font(.title) .fontWeight(.bold) .padding(.bottom, 5) Text(viewModel.verificationResult) .font(.title) .fontWeight(.bold) .foregroundStyle(viewModel.verificationFailedReason == nil ? .green : .red) .padding(.bottom, 5) if let verificationFailedReason = viewModel.verificationFailedReason { Text(verificationFailedReason) .font(.title3) .fontWeight(.bold) .foregroundStyle(.red) .padding(.bottom, 5) } ForEach(viewModel.namespacesAndClaims.keys.sorted(), id: \.self) { key in VStack(alignment: .leading, spacing: 5) { Text(key) .font(.headline) .padding(.vertical, 5) .padding(.horizontal, 10) .background(Color.gray.opacity(0.2)) .cornerRadius(5) ForEach(viewModel.namespacesAndClaims[key]!.keys.sorted(), id: \.self) { claim in HStack { Text(claim) .fontWeight(.semibold) Spacer() Text(viewModel.namespacesAndClaims[key]![claim]! ?? "") .fontWeight(.regular) } .padding(.vertical, 5) .padding(.horizontal, 10) .background(Color.white) .cornerRadius(5) .shadow(radius: 1) } } .padding(.vertical, 5) } if !viewModel.claimErrors.isEmpty { Text("Failed Claims:") .font(.headline) .padding(.vertical, 5) ForEach(viewModel.claimErrors.keys.sorted(), id: \.self) { key in VStack(alignment: .leading, spacing: 5) { Text(key) .font(.headline) .padding(.vertical, 5) .padding(.horizontal, 10) .background(Color.gray.opacity(0.2)) .cornerRadius(5) ForEach(viewModel.claimErrors[key]!.keys.sorted(), id: \.self) { claim in HStack { Text(claim) .fontWeight(.semibold) Spacer() Text(viewModel.claimErrors[key]![claim]! ?? "") .fontWeight(.regular) } .padding(.vertical, 5) .padding(.horizontal, 10) .background(Color.white) .cornerRadius(5) .shadow(radius: 1) } } .padding(.vertical, 5) } } } .padding() .background(RoundedRectangle(cornerRadius: 10).fill(Color.white).shadow(radius: 5)) .padding(.horizontal) } } // MARK: DocumentViewModel @Observable class DocumentViewModel { let docType: String let namespacesAndClaims: [String: [String: String?]] let claimErrors: [String: [String: String?]] let verificationResult: String let verificationFailedReason: String? init(from presentation: MobileCredentialPresentation) { self.docType = presentation.docType self.verificationResult = presentation.verificationResult.verified ? "Verified" : "Invalid" self.verificationFailedReason = presentation.verificationResult.failureType?.rawValue self.namespacesAndClaims = presentation.claims?.reduce(into: [String: [String: String]]()) { result, outerElement in let (outerKey, innerDict) = outerElement result[outerKey] = innerDict.mapValues { $0.textRepresentation } } ?? [:] self.claimErrors = presentation.claimErrors?.reduce(into: [String: [String: String]]()) { result, outerElement in let (outerKey, innerDict) = outerElement result[outerKey] = innerDict.mapValues { "\($0)" } } ?? [:] } } // MARK: Helper extension MobileCredentialElementValue { var textRepresentation: String { switch self { case .bool(let bool): return "\(bool)" case .string(let string): return string case .int(let int): return "\(int)" case .unsigned(let uInt): return "\(uInt)" case .float(let float): return "\(float)" case .double(let double): return "\(double)" case let .date(date): let dateFormatter = DateFormatter() dateFormatter.dateStyle = .short dateFormatter.timeStyle = .none return dateFormatter.string(from: date) case let .dateTime(date): let dateFormatter = DateFormatter() dateFormatter.dateStyle = .short dateFormatter.timeStyle = .short return dateFormatter.string(from: date) case .data(let data): return "Data \(data.count) bytes" case .map(let dictionary): let result = dictionary.mapValues { value in value.textRepresentation } return "\(result)" case .array(let array): return array.reduce("") { partialResult, element in partialResult + element.textRepresentation } .appending("") @unknown default: return "Unknown type" } } } ``` The `DocumentView` file comprises the following elements: * `DocumentView` : Basic UI layout for viewing received documents and verification results. * `DocumentViewModel` : This class takes `MobileCredentialPresentation` and converts its elements into strings that are displayed in the `DocumentView`. * Extension of `MobileCredentialElementValue` which converts the values of received claims into a human-readable format. 4. Return to the `ContentView` file and replace the `EmptyView()` under the `Step 4.4: Create PresentationResponseView` comment with the following code to display the `DocumentView` view when verification results are available: ```swift title="ContentView" ZStack { if viewModel.receivedDocuments.isEmpty { VStack(spacing: 40) { Text("Waiting for response...") .font(.title) ProgressView() .progressViewStyle(.circular) .scaleEffect(2) } } else { ScrollView { ForEach(viewModel.receivedDocuments, id: \.docType) { doc in DocumentView(viewModel: DocumentViewModel(from: doc)) .padding(10) } } } } ``` To enable the redirect back to your verifier application you must register a redirection link. This could be either [App Links](https://developer.android.com/training/app-links/about) or a [Custom deep links](https://developer.android.com/training/app-links/create-deeplinks). In this tutorial you will use a custom deep link. 1. In your app's `AndroidManifest.xml` file, add the following activity declaration to register the callback url: ```xml title="AndroidManifest.xml" ``` * `android:scheme` : This must match the package name you defined when you [created the verifier application](#create-a-verifier-application-configuration). * `android:host` : This must match the redirect host you defined when you [created the verifier application](#create-a-verifier-application-configuration). 2. Add the following code under the `Step 4.2: Handle response` to navigate the user to the response screen, where they can see the retrieved credentials, if the retrieval was successful: ```kotlin title="MainActivity.kt" _receivedDocuments.value = when (onlinePresentationResult) { is OnlinePresentationSessionResult.Success -> onlinePresentationResult.mobileCredentialResponse?.credentials ?: emptyList() is OnlinePresentationSessionResult.Failure -> { // onlinePresentationResult.error is available here emptyList() } } ``` 3. Create a new file named `DocumentView.kt` that will be used to display the response to the verifier application user. 4. Copy and paste the following code into the new file: ```kotlin title="DocumentView.kt" package com.example.mobileverifiertutorial import androidx.compose.foundation.layout.* import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import global.mattr.mobilecredential.verifier.dto.MobileCredentialPresentation @Composable fun DocumentView(document: MobileCredentialPresentation, modifier: Modifier = Modifier) { val verified: Boolean = document.verificationResult.verified val statusText: String = if (verified) "Verified" else "Invalid" val statusColor: Color = if (verified) Color.Green else Color.Red val flatClaims: List = document.claims?.flatMap { (_, claimsMap) -> claimsMap.map { (claim, value) -> "$claim: ${value.value}" } } ?: emptyList() Card( modifier = modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = Color.White), elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) ) { Column(modifier = Modifier.padding(16.dp)) { Text( text = document.docType, color = Color.Black, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, ) Spacer(modifier = Modifier.height(4.dp)) Text( text = statusText, color = statusColor, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold ) Spacer(modifier = Modifier.height(12.dp)) if (flatClaims.isEmpty()) { Text( text = "No claims", color = Color.Black, style = MaterialTheme.typography.labelMedium, ) } else { flatClaims.forEach { line -> Text( text = line, color = Color.Black, style = MaterialTheme.typography.labelMedium ) } } } } } ``` 5. Back in the `MainActivity.kt` file, add the following code under the `Step 4.5: Display received documents` comment: ```kotlin title="MainActivity.kt" DocumentView(document) ``` After the wallet presents the credential, the user is redirected back to your app and the SDK makes the verification results available. Handling this redirect differs by platform: * **iOS**: The wallet redirects back to your app via the custom URL scheme you registered in `app.config.ts`. You must forward the redirect URL to the SDK so it can complete the pending `requestMobileCredentials` call. * **Android**: The `withOpenid4VpCallbackActivity` plugin you configured in [Step 1: Environment setup](#step-1-environment-setup) declared the SDK's `Openid4VpCallbackActivity` in `AndroidManifest.xml`. The SDK handles the redirect automatically, so `requestMobileCredentials` resolves directly with the result and no additional code is required. 1. In your `App.tsx` file, add the following code under the `// Step 4.1: Handle the wallet redirect (iOS)` comment to forward the redirect URL to the SDK on iOS: ```tsx title="App.tsx" useEffect(() => { if (Platform.OS !== "ios") { return; } const subscription = Linking.addEventListener("url", ({ url }) => { handleDeepLink({ url }); }); return () => subscription.remove(); }, []); ``` This passes the redirect URL to the SDK's [`handleDeepLink`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/functions/handleDeepLink.html) method, which processes the response from the wallet and resolves the pending `requestMobileCredentials` call so it returns the verification results. 2. Create a new file named `VerificationResultsModal.tsx` and paste the following code to display the retrieved verification results: ```tsx title="VerificationResultsModal.tsx" import type { MobileCredentialResponse } from "@mattrglobal/mobile-credential-verifier-react-native"; import { Modal, SafeAreaView, ScrollView, Text, TouchableOpacity, View } from "react-native"; import { styles } from "./styles"; interface VerificationResultsModalProps { visible: boolean; onClose: () => void; verificationResults: MobileCredentialResponse | null; } export function VerificationResultsModal({ visible, onClose, verificationResults }: VerificationResultsModalProps) { if (!visible || !verificationResults) return null; // mDoc claims can have various types (string, number, date, array, object, etc.). // Arrays and objects are serialized to JSON; all other types use String conversion. function renderClaimValue(claim: any): string { if (!claim) return "undefined"; if (claim.type === "array" || claim.type === "object") { return JSON.stringify(claim.value); } return String(claim.value); } return ( Verification Results Close {verificationResults.credentials && verificationResults.credentials.length > 0 ? ( {/* Overall verification status for the first credential, as determined by the tenant. */} {verificationResults.credentials[0].verificationResult?.verified ? "✓ Verified" : "✗ Verification Failed"} {verificationResults.credentials[0].docType} {/* Claims organized by namespace. */} {verificationResults.credentials.map((credential, credIndex) => ( {credential.claims && Object.keys(credential.claims).map((namespace, nsIndex) => ( {namespace} {credential.claims && Object.entries(credential.claims[namespace]).map(([key, value], idx) => ( {key}: {renderClaimValue(value)} ))} ))} {/* Verification failure reason, if the mDoc did not pass verification. */} {!credential.verificationResult?.verified && credential.verificationResult?.reason && ( Verification Failed: Type: {credential.verificationResult.reason.type} Message: {credential.verificationResult.reason.message} )} {/* Claim errors: claims that were requested but not provided. */} {credential.claimErrors && Object.keys(credential.claimErrors).length > 0 && ( Claim Errors {Object.entries(credential.claimErrors).map(([namespace, errors]) => Object.entries(errors).map(([elementId, errorCode]) => ( {namespace}.{elementId}: Error: {errorCode} )) )} )} ))} ) : ( No data available )} ); } ``` This component reads each `MobileCredentialPresentation` from the `MobileCredentialResponse` and renders the docType, the overall verification result, the received claims grouped by namespace, and any claim errors. 3. Back in `App.tsx`, uncomment the `VerificationResultsModal` import at the top of the file: ```tsx title="App.tsx" import { VerificationResultsModal } from "./VerificationResultsModal"; ``` 4. Add the following code under the `{/* Step 4.3: Display the verification results */}` comment to render the modal when results are available: ```tsx title="App.tsx" setShowVerificationResults(false)} verificationResults={verificationResults} /> ``` ## Test the end-to-end workflow [#test-the-end-to-end-workflow] 1. Run the app. 2. Select the **Request credentials** button.\ You should be redirected to a compliant wallet application, where you will see the verification request details and choose what mDoc to present for verification. 3. Use the wallet application to present the requested mDoc.\ You will be redirected back to the verifier application where you will see the verification results. You should see a result similar to the following: The React Native app follows the same end-to-end flow as the iOS and Android apps shown above: the verifier app starts a presentation session, redirects to a compliant wallet, and displays the verification results returned by the MATTR VII tenant once the user consents to share the requested information. 1. The verifier app starts a presentation session and gets redirected. 2. The user is redirected to a compliant wallet application. 3. The user provides their consent to share the requested information. 4. The wallet application sends the response back to the MATTR VII tenant. 5. The MATTR VII tenant redirects the user back to the verifier app with the verification results. 6. The verifier app fetches the result and presents the result to user. Congratulations! Your verifier application can now verify mDocs presented from a compliant wallet installed on the same mobile device. ## Summary [#summary] You have just used the [mDocs Mobile Verifier SDKs](/docs/verification/remote-mobile-verifiers/sdks/overview) to build an application that can verify an [mDoc](/docs/concepts/mdocs) presented from a compliant wallet on the same device using a remote presentation workflow as per OID4VP and ISO/IEC 18013-7 Annex B. This was achieved by building the following capabilities into the application: * Initialize the SDK, so that your application can use its functions and classes. * Request an mDoc for verification from a compliant wallet application. * Display verification results in your verifier application. ## What's next? [#whats-next] * You can check out the SDKs reference documentation to learn more about available functions and classes: * [iOS](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk) * [Android](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/index.html) * [React Native](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest) # mDocs remote mobile app verification URL: /docs/verification/remote-mobile-verifiers/workflow mDocs are digital credentials based on the ISO/IEC [18013-5](https://www.iso.org/standard/69084.html) standard and [18013-7](https://www.iso.org/standard/91154.html) technical specification, designed to be stored on a holder’s mobile device and support either in-person or remote verification workflows. The purpose of this page is to describe the end-to-end remote mobile app verification workflow, in which a user interacts with a verifier mobile app and then uses another mobile app, installed on the same device, to present an mDoc for verification. ## Prerequisites [#prerequisites] We recommend you make yourself familiar with the following concepts to support your understanding of the implementation described in this page: * What is [credential verification](/docs/verification)? * What are [mDocs](/docs/concepts/mdocs)? * What is [remote verification](/docs/verification/remote-overview)? ## Overview [#overview] Remote mobile app verification of mDocs is based on [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). Let's use a simple example to illustrate how such a workflow might look like: Remote mobile app verification overview 1. A relying party uses the Verifier SDK to embed a remote verification workflow into an existing mobile verifier application. For example, a bank embeds remote verification as part of the workflow for opening a new bank account on their mobile banking app. 2. When a user interacts with the mobile app, a matching wallet application installed on their mobile device is invoked to request sharing information held in an mDoc. In our example, the user attempts to open a new bank account in the bank's mobile banking app, and is requested to provide a supporting identity document, such as a Mobile Drivers License (mDL). 3. The user consents to sharing the requested information. In our example, the user has already claimed an mDL as an mDoc into their wallet application in a previous interaction, and can now share that credential to prove their address. 4. The user's wallet application shares the matching mDoc with the MATTR VII tenant configured by the Verifier SDK to perform the verification workflow. 5. The MATTR VII tenant performs the required checks and returns the verification results via the Verifier SDK to the verifier application. 6. The user journey continues based on the verification results and the relying party business logic built into the verifier application. In our example, pending successful verification of the mDoc, the user can proceed to opening a new bank account. ## Detailed workflow [#detailed-workflow] Let's take a closer look at the end-to-end workflow and explain the role of each component: ### The user triggers the workflow [#the-user-triggers-the-workflow] The workflow is triggered when the user interacts with a mobile application that requires presenting information stored in an mDoc. For example, a user opening a bank account in their banking app may be asked to provide an identity document. They can do this by presenting an mDoc stored in a wallet application on the same mobile device. ### The Verifier SDK starts a presentation-based verification session [#the-verifier-sdk-starts-a-presentation-based-verification-session] The verifier application uses the Verifier SDK to send a request to a MATTR VII verifier tenant to start a remote, presentation-based verification session. This request is based on the Verifier SDK configuration that defines: * The credential query: * What credentials are required. * What specific claims are required from these credentials. * What MATTR VII tenant to interact with. * A unique identifier of this mobile application. * What [protocol](/docs/verification/remote-overview#verification-requests) to use for the interaction. On the MATTR VII side, the tenant is configured to determine how to handle requests from different [applications](/docs/verification/remote-verification-api-reference/verifier-applications#create-a-verifier-application) based on the application identifier provided in the request. ### The MATTR VII verifier tenant responds with a link to invoke a matching application [#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application] The MATTR VII verifier tenant responds with a URI, referred to as the *Authorization endpoint* in OID4VP. This endpoint is configured when you [create a trusted wallet provider](/docs/verification/remote-verification-api-reference/wallet-providers#create-a-wallet-provider) and defines the URI to invoke an application based on its unique identifier. As per OID4VP, the Authorization endpoint can be one of the following: * **Default URI scheme**: This is a generic URI scheme defined by the OID4VP specification (`openid-vp://`) that can be handled by any compliant application. * If you are interacting with the link from an app that already recognizes this type of scheme, it can handle the request immediately. * If you are interacting with the link outside of the app (such as tapping a link in a browser or scanning a QR code with the native camera), this will typically prompt the OS to present options of any app that is registered to handle that scheme (on Android), or sometimes it will automatically pick an app without enabling user selection (on iOS). This approach is less secure as **any application** registered to handle this scheme could respond to the request, which might lead to phishing attacks where a malicious app pretends to be a trusted one. * **Custom URI**: The [OAuth 2.0 for Native Apps](https://datatracker.ietf.org/doc/html/rfc8252) specification proposes using more unique custom schemes to make it clearer that the link is intended for a particular app. This uses reverse-domain schemes, such as `com.example.app://`. This works in the same way as the default OID4VP custom scheme, but makes it less likely that another app would be registered to handle the same reverse domain. * **HTTP link**: Also called out in the [OAuth 2.0 for Native Apps](https://datatracker.ietf.org/doc/html/rfc8252) specification, the most secure way to ensure only a specific app can handle a link is to register a domain path to open your app via App Links on Android and Universal Links on iOS. This is an HTTPS URL pointing to your domain, which the operating system verifies is registered to only open a particular app. It provides the smoothest user experience as the OS can reliably open the correct app without presenting choices. HTTP links are recommended by the OID4VP specification as they are more secure. These URIs are verified against the domain owner before launching the application, as the domain must host an association file declaring the app's link. This ensures the provider owns both the app and the domain. Additionally, it is recommended to host a webpage for this URI to allow the user to continue the interaction if the HTTP link fails to invoke the intended application. For example, the user might not have the application installed and in this case the page should include a link to enable them to download the app. HTTP links require domain ownership and proper configuration of verification files on your web server. More information can be found in the following resources: * [iOS - Universal Links](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) * [Android - App Links](https://developer.android.com/training/app-links/verify-site-associations) To use a custom URI scheme or App Link/Universal Link, the *Authorization endpoint* must be properly configured on the MATTR VII verifier tenant when creating a trusted wallet provider, for example: * Custom URI scheme: `com.example.app://openid-vp` (where `com.example.app` is the app's reverse domain and `://openid-vp` is the app path that can handle the request) * HTTP link: `https://example.com/openid-vp` (where `example.com` is a domain registered to open the specific app and configured with proper verification files) The verifier's website can also include a UI element to allow users to select the app they want to use for presenting credentials. Based on this selection, the Verifier Web SDK will identify the application and send the corresponding identifier to the MATTR VII verifier tenant. The tenant will then return the associated URI (Authorization endpoint). Regardless of which URI scheme approach you choose, holder apps you expect to verify credentials from must be properly registered with the operating system to handle these schemes. See the following resources for platform-specific implementation guides: * [Apple Docs - Defining a Custom URL Scheme for Your App](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) * [Android Docs - Create Deep Links to App Content](https://developer.android.com/training/app-links/deep-linking) ### The link is used to invoke a compliant application [#the-link-is-used-to-invoke-a-compliant-application] The link is used by the Verifier SDK to redirect the user to a matching wallet application installed on the same mobile device. When using a Custom URI a dialogue box appears on the mobile device, prompting them to launch a matching application (this dialogue box is not displayed for HTTP links). ### The wallet application makes a request to retrieve a request object [#the-wallet-application-makes-a-request-to-retrieve-a-request-object] The invoked wallet application can now use the link to retrieve the request object from the MATTR VII verifier tenant. This is a presentation request that defines what information is requested by the verifier and for what purpose. In [OAuth 2.0 terminology](https://datatracker.ietf.org/doc/html/rfc6749#section-1.1) this is referred to as an Authorization request, made by the MATTR VII verifier tenant which acts as a **Client**. The request is sent to the wallet application which acts as an **Authorization server**, as it controls access to protected resources, in this case stored credentials. ### The MATTR VII tenant returns the request object [#the-mattr-vii-tenant-returns-the-request-object] The MATTR VII verifier tenant generates the request object, signs it with its verifier certificate and sends it as a response to the wallet application's request. The wallet application will handle the received request object and perform the following: * Establish trust with the verifier in one of the following methods: * **Certificate trust model**: The wallet application compares the certificate used to sign the request object against its list of trusted verifiers. This would be the [`certificatePem`](/docs/verification/certificates/api-reference/verifier-root-ca-certificates#create-a-verifier-root-ca-certificate) element of the Verifier root CA, and must be provided by the verifier to the holder out of band. * **Domain trust model**: The wallet application retrieves the verifier metadata by making a request to the `/.well-known/oauth-client` endpoint of the domain the request object is coming from. By default this would be the verifier's [`tenant_url`](/docs/platform-management/portal#getting-started). When using a [Custom domain](/docs/platform-management/custom-domain-overview), you must configure a [redirect](/docs/platform-management/custom-domain-guide#create-redirects-for-required-assets) for that path from your custom domain to your MATTR VII verifier tenant. * Gather credentials matching the information provided in the request object. * Display this information to the holder and ask for consent to sharing it with the relying party. The MATTR VII verifier tenant uses a [verifier root CA certificate](/docs/verification/certificates/api-reference/verifier-root-ca-certificates#create-a-verifier-root-ca-certificate) to identify itself when interacting with wallet applications. ### The wallet application sends an authorization response [#the-wallet-application-sends-an-authorization-response] Once the user consents to sharing the information, the wallet application generates an authorization response, which includes a signed verifiable presentation of matching credentials contained in a `vp_token` parameter. The authorization response is encrypted and sent to the MATTR VII verifier tenant. Upon receiving the authorization response, the MATTR VII verifier tenant would decrypt it and [verify](/docs/verification/remote-overview) the contained verifiable presentation. This can include checking whether the certificate used to sign the credential matches an existing issuer in the tenant's trusted issuers list. ### The MATTR VII verifier tenant returns the verification results [#the-mattr-vii-verifier-tenant-returns-the-verification-results] The MATTR VII tenant responds with a redirect URI which is used by the wallet application to redirect the user back to continue the verifier application. This redirect URI can be configured per verifier application, enabling redirecting users to different targets based on the verifier application they are interacting with. When the verifier application has a backend, the MATTR VII verifier tenant can be configured to only send the results to the backend rather than to the frontend directly. This enhances security as described in the following flow: 1. The backend creates a unique challenge when the session is established. 2. The verifier application passes the unique challenge with the request to start a presentation session. 3. The verifier application is notified when the MATTR VII tenant had completed verification. 4. The verifier application passes the session ID to the backend. 5. The backend makes a [request](/docs/verification/remote-verification-api-reference/presentation-sessions#retrieve-a-presentation-session-result) to MATTR VII to retrieve the verification results for that session. 6. The MATTR VII tenant responds with the verification result and the unique challenge. 7. The backend compares the original and the received challenge to ensure the response can be trusted. 8. The backend passes the verification results to the verifier application. Results availability can be configured per verifier application. ### The verifier application surfaces verification results [#the-verifier-application-surfaces-verification-results] The user journey can now proceed based on the verifier application business logic. In our example, if the presented mDoc was verified the user can proceed to opening a bank account, whereas if verification failed they will receive an error message. # Apple Identity Access CSRs URL: /docs/verification/remote-verification-api-reference/apple-identity-access-csr ## Create an Apple Identity Access certificate signing request [#create-an-apple-identity-access-certificate-signing-request] ## Retrieve all Apple Identity Access certificate signing requests [#retrieve-all-apple-identity-access-certificate-signing-requests] ## Retrieve an Apple Identity Access certificate signing request [#retrieve-an-apple-identity-access-certificate-signing-request] ## Delete an Apple Identity Access certificate signing request [#delete-an-apple-identity-access-certificate-signing-request] # Presentation sessions URL: /docs/verification/remote-verification-api-reference/presentation-sessions ## Retrieve a presentation session result [#retrieve-a-presentation-session-result] # Trusted issuers URL: /docs/verification/remote-verification-api-reference/trusted-issuers ## Create a trusted issuer [#create-a-trusted-issuer] ## Retrieve all trusted issuers (protected) [#retrieve-all-trusted-issuers-protected] ## Retrieve all trusted issuers (public) [#retrieve-all-trusted-issuers-public] ## Retrieve a trusted issuer [#retrieve-a-trusted-issuer] ## Delete a trusted issuer [#delete-a-trusted-issuer] # Verifier applications URL: /docs/verification/remote-verification-api-reference/verifier-applications ## Create a verifier application [#create-a-verifier-application] ## Retrieve all verifier applications [#retrieve-all-verifier-applications] ## Retrieve a verifier application [#retrieve-a-verifier-application] ## Update a verifier application [#update-a-verifier-application] ## Delete a verifier application [#delete-a-verifier-application] # Wallet providers URL: /docs/verification/remote-verification-api-reference/wallet-providers ## Create a wallet provider [#create-a-wallet-provider] ## Retrieve all wallet providers [#retrieve-all-wallet-providers] ## Retrieve a wallet provider [#retrieve-a-wallet-provider] ## Update a wallet provider [#update-a-wallet-provider] ## Delete a wallet provider [#delete-a-wallet-provider] # mDocs remote web verification journey pattern URL: /docs/verification/remote-web-verifiers/journey-pattern This journey pattern is used to verify an mDoc remotely via an [online verification workflow](/docs/verification/remote-overview), as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). ## Overview [#overview] * **Issuance channel**: Remote, unsupervised * **Device/s**: Same-device / Cross-device * **Formats**: mDocs * **Information assurance level**: High * **Identity assurance level**: High ## Journey flow - Same-device [#journey-flow---same-device] mDocs Web Verification Same-device ## Architecture - Same-device [#architecture---same-device] Remote web verification same-device architecture ### Interacting with the website [#interacting-with-the-website] The user accesses a website using a mobile browser on the same device that holds their wallet app. ### Requesting a credential for verification [#requesting-a-credential-for-verification] On the website, the user begins an interaction that requires them to present a credential. The MATTR Pi Verifier Web SDK, embedded in the web application, initiates the verification request by sending it to a configured MATTR VII verifier tenant. This request defines: * The credentials and claims required * The supported interaction modes (same-device, in this case) The MATTR VII verifier tenant is configured with: * The domains it can accept requests from * The workflows it supports (e.g. same-device and/or cross-device) * The supported protocols (e.g. Digital Credentials API and/or OID4VP) * The wallet applications it can interact with * How to invoke these wallet applications on the same device Based on this configuration, the MATTR VII verifier tenant identifies that the user is using a mobile browser and returns a universal link or custom URI that can invoke the wallet app. The Verifier Web SDK uses this link to redirect the user to their wallet application. ### Presenting request details to the user [#presenting-request-details-to-the-user] Once the wallet is launched, it authenticates the user and retrieves the verification request from the MATTR VII verifier tenant. The user is shown: * The credentials being requested * The claims required from those credentials * Whether the relying party is trusted and authorized by the Digital Trust Service * Which of the user’s credentials match the request The user can then review and consent to sharing the information. ### Verifying the credential [#verifying-the-credential] The MATTR VII verifier tenant verifies the presentation by checking: * That the credential has not been tampered with * That it has not been revoked or suspended * That it has not expired * That it was issued by a trusted issuer, validated via the configured Digital Trust Service ### Displaying verification results [#displaying-verification-results] After the verification is complete, the wallet app redirects the user back to their mobile browser, returning them to the original web application using the previously provided redirect URl. The Verifier Web SDK receives the verification result and renders it in the browser, allowing the user to view the outcome and continue their interaction. The issuer of the credential is not informed that the presentation has occurred. No data about the verifier, the context of use, or the interaction itself is shared with the issuer. The only interaction with the issuer is a potential call to an online revocation endpoint, if revocation checking is required. The MATTR VII verifier tenant can also be configured to return the verification result to a secure back-end service instead of the front-end, depending on implementation needs. ## Journey flow - Cross-device [#journey-flow---cross-device] mDocs Web Verification Cross-device ## Architecture - Cross-device [#architecture---cross-device] Remote verification mobile architecture ### Interacting with the website [#interacting-with-the-website-1] The user accesses a website using a web browser on their desktop. ### Requesting a credential for verification [#requesting-a-credential-for-verification-1] On the website, the user begins an interaction that requires them to present a credential. The MATTR Pi Verifier Web SDK, embedded in the web application, initiates the verification request by sending it to a configured MATTR VII verifier tenant. This request defines: * The credentials and claims required * The supported interaction modes (same-device, in this case) The MATTR VII verifier tenant is configured with: * The domains it can accept requests from * The workflows it supports (e.g. same-device and/or cross-device) * The supported protocols (e.g. Digital Credentials API and/or OID4VP) * The wallet applications it can interact with * How to invoke these wallet applications on the same device Based on this configuration, the MATTR VII verifier tenant returns a link. The Verifier Web SDK recognizes Samantha is using a desktop browser and renders this link as a QR code on the webpage. The user scans the QR code using a mobile device that has a compatible digital wallet installed. This action invokes the wallet app with the verification request. ### Presenting request details to the user [#presenting-request-details-to-the-user-1] Once the wallet is launched, it authenticates the user and retrieves the verification request from the MATTR VII verifier tenant. The user is shown: * The credentials being requested * The claims required from those credentials * Whether the relying party is trusted and authorized by the Digital Trust Service * Which of the user’s credentials match the request The user can then review and consent to sharing the information. ### Verifying the credential [#verifying-the-credential-1] The MATTR VII verifier tenant verifies the presentation by checking: * That the credential has not been tampered with * That it has not been revoked or suspended * That it has not expired * That it was issued by a trusted issuer, validated via the configured Digital Trust Service ### Displaying verification results [#displaying-verification-results-1] The MATTR VII verifier tenant shares the verification results with the Verifier Web SDK. These results are displayed to the user in the browser, allowing them to continue their interaction The issuer of the credential is not informed that the presentation has occurred. No data about the verifier, the context of use, or the interaction itself is shared with the issuer. The only interaction with the issuer is a potential call to an online revocation endpoint, if revocation checking is required. The MATTR VII verifier tenant can also be configured to return the verification result to a secure back-end service instead of the front-end, depending on implementation needs. # Web verification quickstart guide URL: /docs/verification/remote-web-verifiers/quickstart This quickstart is for evaluating the MATTR Pi Verifier Web SDK. In about 15-20 minutes you will configure a MATTR VII verifier tenant, run a sample web verifier application, and verify an mDoc presented remotely from the MATTR GO Hold example wallet using the remote presentation workflow defined by OID4VP and ISO/IEC 18013-7:2025. **Estimated time: 15-20 minutes.** Use this guide as a fast evaluation path to see the Verifier Web SDK working end-to-end. For more detailed instructions and API examples, see the [tutorial](/docs/verification/remote-web-verifiers/tutorial) and reference documentation. Using the [Verifier Web SDK](/docs/verification/remote-web-verifiers/sdks/overview) to verify an mDoc presented remotely to a web application: ## User experience [#user-experience] In this quickstart you will perform both of these flows yourself using a MATTR VII verifier tenant, the Verifier Web SDK sample application, and the MATTR GO Hold example app: Tutorial Workflow 1. The user interacts with your web application on their mobile device browser. 2. The user is asked to present information as part of the interaction. 3. The user is redirected to their wallet application. 4. The user is informed of what information they are about to share and provide their consent. 5. The wallet application authenticates the user and shares the information with the verifier. 6. The user is redirected back to the web application where verification results are displayed, enabling them to continue with the interaction. Tutorial Workflow 1. The user interacts with your web application on their desktop browser. 2. The user is asked to present information as part of the interaction. 3. The user scans a QR code using a mobile device with an installed wallet application. 4. The wallet application is launched on the mobile device. 5. The user is informed of what information they are about to share and provide their consent. 6. The wallet application authenticates the user and shares the information with the verifier. 7. Verification results are displayed in the user's desktop browser, enabling them to continue with the interaction. ## Prerequisites [#prerequisites] * MATTR VII tenant access via the [MATTR Portal](https://portal.mattr.global/). Apply for access [here](/docs/resources/get-started). * Install the **MATTR GO Hold example app** for [iOS](https://apps.apple.com/app/mattr-wallet/id1518660243) or [Android](https://play.google.com/store/apps/details?id=global.mattr.wallet). * Sign up for a free [ngrok.com](https://ngrok.com/) account and make note of your ngrok authentication token. We will be using ngrok to expose your local web application to the internet. ## Part 1: Configure the MATTR VII verifier tenant (5 minutes) [#part-1-configure-the-mattr-vii-verifier-tenant-5-minutes] These steps configure your verifier tenant so the sample web verifier application can request and verify mDocs issued by a trusted issuer. ### Configure a Verifier application on MATTR VII [#configure-a-verifier-application-on-mattr-vii] This creates the web verifier application that the Verifier Web SDK will use when interacting with the MATTR VII tenant to request and receive credential presentations. 1. Log into the [MATTR Portal](https://portal.mattr.global/). 2. Switch to your tenant if you have access to multiple tenants, or [create a new tenant](/docs/platform-management/portal#creating-a-tenant) if needed. 3. Expand the **Credential Verification** section in the left-hand navigation panel. 4. Select **Applications**. 5. Select the **Create new** button. 6. Use the *Name* text box to insert a name for your application (e.g. `My Web Verifier`). 7. Use the *Type* radio button to select **Web**. 8. Use the *Allowed domains* text box to insert a placeholder domain name (e.g. `place-holder.com`).\ You will update this placeholder value later. 9. Use the *Redirect URIs* text box to insert a placeholder redirect URI (e.g. `https://place-holder.com/redirect`).\ You will update this placeholder value later. 10. Select the **Create** button to create the new application and display the *Application detail* screen. 11. Copy and record the `ID` value. You will use it in the next step. ### Configure a trusted issuer [#configure-a-trusted-issuer] In this quickstart you will add a MATTR-provided demo issuer certificate so your verifier accepts mDocs issued by this issuer. 1. Select **Trusted issuers** under the **Credential Verification** section. 2. Select the **Create new** button. 3. Copy and paste the following certificate into the dialogue box. ``` -----BEGIN CERTIFICATE----- MIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG EwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew HhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp MCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq hkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp dB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud EwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq 232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu bWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp ZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp bGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny bDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI SNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6 -----END CERTIFICATE----- ``` 4. Click the **Add** button. ## Part 2: Run the Sample Web Verifier Application (5-10 minutes) [#part-2-run-the-sample-web-verifier-application-5-10-minutes] In this section you will clone, configure, and run a sample web verifier application that uses the Verifier Web SDK. ### Clone the sample application repository [#clone-the-sample-application-repository] ```bash title="Clone the Sample Apps repository" git clone https://github.com/mattrglobal/sample-apps.git ``` ### Configure the sample application [#configure-the-sample-application] Configure the sample web verifier with details required to communicate with your MATTR VII tenant and verifier application. 1. Navigate to the sample web verifier application directory: ```bash title="Navigate to the sample web verifier application directory" cd sample-apps/verifier-web-tutorial ``` 2. Rename the `env-template` file to `.env`: ```bash title="Rename the env-template file" mv env-template .env ``` 3. Open the `.env` file in your code editor and only update the following values: * `NEXT_PUBLIC_TENANT_URL`: Your MATTR VII tenant URL, available in the MATTR Portal under **Platform Management > Tenant**. * `NEXT_PUBLIC_APPLICATION_ID`: Unique identifier of the Verifier application you created in the previous step. * `NGROK_AUTHTOKEN`: Your ngrok authentication token. This is how the values in the updated `.env` file should look like: ``` NEXT_PUBLIC_TENANT_URL="https://learn.vii.au01.mattr.global" NEXT_PUBLIC_APPLICATION_ID="84a9c4e0-e597-4183-b231-3d0d699104a6" NGROK_AUTHTOKEN="2bKVt************************************" ``` ### Start the application [#start-the-application] 1. Start the application: ```bash title="Start the application" npm install npm run dev ``` 2. Open your terminal and copy the public URL provided by ngrok (e.g. `https://abc-123-xyz.ngrok-free.app`). You should now see the sample web verifier application running and reachable at your ngrok public URL. ### Update the Verifier application configuration [#update-the-verifier-application-configuration] Now point your verifier application configuration at the public URL of the sample web verifier application so the Verifier Web SDK can complete the remote presentation flow. 1. Go back to the MATTR Portal. 2. Expand the **Credential Verification** section in the left-hand navigation panel. 3. Select **Applications**. 4. Select the Verifier application you created in the first step. 5. Select the **Edit** button. 6. Replace the placeholder domain in the *Allowed domains* text box with the ngrok domain from the previous step. Make sure to remove the `https://` prefix (e.g. `abc-123-xyz.ngrok-free.app`). 7. Replace the placeholder redirect URI in the *Redirect URIs* text box with a URI on the same ngrok domain. Make sure to include the `https://` prefix (e.g. `https://abc-123-xyz.ngrok-free.app`). 8. Select the **Update** button. ## Part 3: Test the application (5 minutes) [#part-3-test-the-application-5-minutes] ### Claim a credential to present [#claim-a-credential-to-present] In order to test the verification flow, you need to have a compatible mDoc in your wallet to present. You can use the MATTR GO Hold example app to claim a demo mDL credential issued by the MATTR demo issuer you added as a trusted issuer in the tenant configuration steps. 1. Open the MATTR GO Hold example app and tap the Blue **Share** button. 2. Select **Respond or Collect** (You may need to allow the app to access your camera). 3. Scan the following QR code: QR Code 4. Follow the on-screen instructions to claim the credential into your MATTR GO Hold example app. This credential will be used in the next step when you test the remote verification flow in the sample web verifier application. ### Test the remote verification flow [#test-the-remote-verification-flow] Next, use the GO Hold app and the sample web verifier to complete both same-device and cross-device remote verification flows. 1. Open the **ngrok public URL** in a browser on a mobile device where the MATTR GO Hold example app is installed. 2. Select the **Request Credential** button. 3. Select **Allow** to open the MATTR GO Hold example app. 4. The app should launch and display what information is requested for verification. 5. Select **Start**. 6. Select **Confirm** to share the credential. 7. The web application should display the verification results. 1. In a desktop browser, open the **ngrok public URL** (Do not use localhost as the cross-device flow requires a publicly accessible URL). 2. Select the **Request Credential** button. A QR code will be displayed. 3. Open the MATTR GO Hold example app and tap the Blue **Share** button. 4. Select **Respond or Collect** (You may need to allow the app to access your camera). 5. Scan the QR code. 6. The app will display what information is requested for verification. 7. Select **Start**. 8. Select **Confirm**. 9. Back on your desktop browser, the web application should display the verification results. ## Understand the codebase [#understand-the-codebase] The sample web verifier application uses the [Verifier Web SDK](/docs/verification/remote-web-verifiers/sdks/overview) to implement the OID4VP flow and verify mDocs presented remotely from a wallet. Once you have the sample running, use this section to inspect the key SDK calls you will reuse in your own application. The main steps are: ### Initialize the SDK [#initialize-the-sdk] In the sample application, this runs during startup so the Verifier Web SDK knows which tenant and verifier application to use: ```javascript title="Initialize the SDK" MATTRVerifierSDK.initialize({ apiBaseUrl: "", applicationId: "", }); ``` * `apiBaseUrl`: The base URL of the MATTR tenant used to handle mDocs verification. * `applicationId`: The ID of a verifier application in the referenced MATTR tenant. ### Create a credential query [#create-a-credential-query] Defining a credential query object specifies the type of credential and the attributes you want to request from the user for verification. For example, the following query requests an mDL with specific attributes: ```javascript title="Create a credential request" const credentialQuery = { profile: "MATTRVerifierSDK.OpenidPresentationCredentialProfileSupported.MOBILE", docType: "org.iso.18013.5.1.mDL", nameSpaces: { "org.iso.18013.5.1": { age_over_18: {}, given_name: {}, family_name: {}, portrait: {}, }, }, }; ``` * `profile` : The profile to use for the credential query. * `docType`: The type of the mDoc to query. * `nameSpaces`: The namespaces and attributes to request from the mDoc. ### Define credential request options [#define-credential-request-options] Defining the credential request options specifies the details of how you want to handle the OID4VP flow, including the credential query, challenge and OID4VP configuration: ```javascript title="Define credential request options" const options = { credentialQuery: [credentialQuery], challenge: generateChallenge(), openid4vpConfiguration: { redirectUri: "", walletProviderId: "", }, }; ``` * `credentialQuery` : Define what information is requested from the user for verification. * `challenge`: A unique challenge string to prevent replay attacks. * `openid4vpConfiguration`: Configuration for the OID4VP flow: * `redirectUri`: The web application to redirect the user to after completing a same-device presentation flow. Must match the redirect URI configured in the Verifier application settings on the MATTR tenant. * `walletProviderId`: The ID of a trusted wallet provider configured in the Verifier application settings on the MATTR tenant. The tenant will only accept mDocs presented by wallets from this provider. Refer to the tutorial to learn how to configure a wallet provider ID for a specific wallet application. ### Request credentials and handle the verification results [#request-credentials-and-handle-the-verification-results] Requesting credentials from the wallet will trigger the OID4VP flow, including a redirection to the wallet for same-device flows or QR code generation for cross-device flows. Once the flow is completed, the results of the verification will be returned to the application: ```javascript title="Request credentials" const results = await MATTRVerifierSDK.requestCredentials(options); ``` Once results are returned, you can parse and display them to the user as per your requirements. This quickstart guide is a basic example of how to use the Verifier Web SDK. To make the solution more secure and representative of real-world integrations, it’s recommended to introduce a backend into the workflow. This “back-channel” approach allows your backend to retrieve and validate verification results directly from your MATTR VII tenant. It also enables you to correlate verification outcomes with user sessions in your own system—something that’s essential for realistic POCs and production deployments. Refer to the [tutorial](/docs/verification/remote-web-verifiers/tutorial) for a more complete example of this approach. ## Next steps [#next-steps] * For your evaluation: * Note how long this quickstart took and any friction you encountered. * Consider how the sample query and result handling would map to your real verification use case (for example, which attributes you’d request and how you’d display them). * Explore the [tutorial](/docs/verification/remote-web-verifiers/tutorial) for detailed instructions and explanations. * Refer to the [DC API](/docs/verification/remote-web-verifiers/dc-api/guide) guide to learn how to implement the same verification flow using the Digital Credentials API to request credentials. # Learn how to build and configure a web application that can verify mDocs URL: /docs/verification/remote-web-verifiers/tutorial ## Introduction [#introduction] In this tutorial, you will use the [MATTR Portal](/docs/platform-management/portal) and the [Verifier Web SDK](/docs/verification/remote-web-verifiers/sdks/overview) to build and configure a web application that can verify an mDoc presented via an [online presentation workflow](/docs/verification/remote-overview) as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). This web application will support both same-device and cross-device workflows to accommodate flexible user journeys. Tutorial Workflow 1. The user interacts with your web application on their mobile device browser. 2. The user is asked to present information as part of the interaction. 3. The user is redirected to their wallet application. 4. The user is informed of what information they are about to share and provide their consent. 5. The wallet application authenticates the user and shares the information with the verifier. 6. The user is redirected back to the web application where verification results are displayed, enabling them to continue with the interaction. The result will look something like this: Tutorial Workflow 1. The user interacts with your web application on their desktop browser. 2. The user is asked to present information as part of the interaction. 3. The user scans a QR code using a mobile device with an installed wallet application. 4. The wallet application is launched on the mobile device. 5. The user is informed of what information they are about to share and provide their consent. 6. The wallet application authenticates the user and shares the information with the verifier. 7. Verification results are displayed in the user's desktop browser, enabling them to continue with the interaction. The result will look something like this: ## Prerequisites [#prerequisites] Before you get started, let's make sure you have everything you need. ### Prior knowledge [#prior-knowledge] * We recommend you make yourself familiar with the following concepts to support your understanding of the concepts included in this tutorial: * What is [Credential verification](/docs/verification)? * What are [mDocs](/docs/concepts/mdocs)? * What steps does an [online verification workflow](/docs/verification/remote-web-verifiers/workflow) comprise? ### Assets [#assets] * Complete the [sign up form](/docs/resources/get-started) to get trial access to MATTR VII and the MATTR Portal. * Use the MATTR Portal to [create a new tenant](/docs/platform-management/portal#getting-started). * Install the **MATTR GO Hold example app** by following the [getting started guide](/docs/holding/go-hold/getting-started) and use it to [claim an mDoc](/docs/holding/go-hold/getting-started#claim-a-credential). * Refer to the completed implementation in our [Sample Apps repository](https://github.com/mattrglobal/sample-apps/tree/master/verifier-web-tutorial) to compare with your progress at any time. ### Development environment [#development-environment] * Download and install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm). * To test locally you will need to expose your web application to the internet. You can do this by setting up a [free ngrok account](https://ngrok.com/), using a cloudflare tunnel or using your own solution. For this tutorial we will be using ngrok. Sign up for a free account at [ngrok.com](https://ngrok.com/). * You will need a code editor such as [VS Code](https://code.visualstudio.com/download). Got everything? Let's get going! ## Overview [#overview] The following diagram depicts the workflow you will build in this tutorial: 1. The user triggers the workflow by interacting with the web application. 2. The customer's backend creates a unique challenge and passes it to the customer's web application. 3. The web application passes the unique challenge with the request to start a presentation session. 4. MATTR VII interacts with the user's wallet application to complete the verification workflow. 5. The web application is notified when the MATTR VII tenant has completed verification. 6. The web application passes the session ID to the backend. 7. The backend makes a [request](/docs/verification/remote-verification-api-reference/presentation-sessions#retrieve-a-presentation-session-result) to MATTR VII to retrieve the verification results for that session. 8. The MATTR VII tenant responds with the verification result and the unique challenge. 9. The backend compares the original and the received challenge to ensure the response can be trusted. 10. The backend passes the verification results to the web application. You will build this workflow in three parts: 1. [Part 1: Use the MATTR Portal to configure the MATTR VII verifier tenant](#part-1-configure-the-mattr-vii-verifier-tenant). 2. [Part 2: Build a web application with mDocs verification capabilities](#part-2-build-a-web-application-with-mdocs-verification-capabilities). 3. [Part 3: Integrate a backend](#part-3-integrate-a-backend-into-your-online-verification-workflow). ## Part 1: Configure the MATTR VII verifier tenant [#part-1-configure-the-mattr-vii-verifier-tenant] The MATTR VII verifier tenant will be used to interact with your web application (generating a verification request) and the wallet application (presenting an mDoc for verification) as per OID4VP and ISO/IEC 18013-7. To enable this, you must: 1. [Create a verifier application configuration](#create-a-verifier-application-configuration): Define what applications can create verification sessions with the MATTR VII tenant, and how to handle these requests. 2. [Create a supported wallet configuration](#create-a-supported-wallet-configuration): Define how to invoke specific wallet applications as part of an online verification workflow. 3. [Configure a trusted issuer](#configure-a-trusted-issuer): The MATTR VII verifier tenant will only accept mDocs issued by these trusted issuers. You can perform these steps via the Portal or by making API requests to your MATTR VII tenant. ### Create a verifier application configuration [#create-a-verifier-application-configuration] Each MATTR VII tenant can interact with multiple verifier applications, and can handle requests differently for each application. This means you must create a verifier application configuration that defines how to handle verification requests from your web application. 1. Expand the **Credential Verification** section in the left-hand navigation panel. 2. Select **Applications**. 3. Select the **Create new** button. 4. Use the *Name* text box to insert a meaningful and friendly name for your application. 5. Use the *Type* radio button to select **Web**. 6. Use the *Allowed domains* text box to insert a placeholder domain name (e.g. `place-holder.com`). This indicates domain names that the MATTR VII tenant can verify incoming requests are from known and trusted applications. You will update this placeholder value later. 7. Use the *Redirect URIs* text box to insert a placeholder redirect URI (e.g. `https://place-holder.com`). This is the URI the user would be redirected to after presenting a credential on a same-device flow. You will update this placeholder value later. 8. Select the **Create** button to create the new application and display the *Application detail* screen. 9. Copy and record the `ID` value. You will use it later in the tutorial. Make the following request to your MATTR VII tenant to [create a verifier application configuration](/docs/verification/remote-verification-api-reference/verifier-applications#create-a-verifier-application): ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Verifier Web Application", "type": "web", "domain": "place-holder.com", "openid4vpConfiguration": { "supportedModes": "all", "redirectUris": ["https://place-holder.com"], "display": { "logoImage": { "url": "https://static.mattr.global/logos/mattr/s/gFC.svg", "altText": "MATTR Logo image" }, "headerText": "Share your information.", "bodyText": "Please scan the QR code to provide information required for completing this interaction." } }, "resultAvailableInFrontChannel": true } ``` * `name` : You can use whatever name you'd like, as long as it is unique on your tenant. * `type` : We will use `web` as we are building a web application. * `domain` : Here we define the web application domain name so that the MATTR VII tenant can verify incoming requests are from known and trusted applications. As `localhost` is not supported we will use a tunneling service (ngrok) to test this tutorial. We will update this placeholder value later. * `openid4vpConfiguration`: * `supportedModes` : Setting this to `all` indicates that your web application will support both same-device and cross-device workflows. * `redirectUris` : This is the URI the user would be redirected to after presenting a credential on a same-device flow. We will update this placeholder value later. * `display` : Adjust the iframe modal appearance in cross-device flows: * `logoImage` : * `url` : Insert any publicly available URL of a logo image which is displayed on the top left corner of the iframe modal. * `altText` : Edit the logo image alternative text. * `headerText` : Edit the header displayed in the iframe modal. * `bodyText` : Edit the text displayed in the iframe modal. * `resultAvailableInFrontChannel` : Setting this to `true` makes the verification results available directly to the web application. You can change this setting later if you choose to [integrate a backend](#part-3-integrate-a-backend-into-your-remote-verification-workflow) into the workflow. *Response* ```json title="Response body" { "id": "0eaa8074-8cc4-41ec-9e42-072d36e2acb0", // [!code focus] "name": "My Verifier Web Application" //... rest of application configuration } ``` * `id` : We will use this value later to initialize the SDK so that requests coming from your web application can be recognized and trusted by the MATTR VII tenant. ### Create a supported wallet configuration [#create-a-supported-wallet-configuration] Verifier web applications can define specific wallet applications to accept mDocs from as part of their verification workflows. The MATTR VII verifier tenant needs to be configured with a specific URI scheme that will be used to invoke these wallets. 1. Expand the **Credential Verification** section in the left-hand navigation panel. 2. Select **Supported wallets**. 3. Select the **Create new** button. 4. Use the *Name* text box to insert a meaningful and friendly name for your wallet application (for example "MATTR GO Hold"). 5. Use the *Authorize endpoint* text box to insert `mdoc-openid4vp://`. This is the URI scheme that will be used to invoke the wallet application. More information on applying different URI schemes and the resulting user experience can be found in the [workflow page](/docs/verification/remote-web-verifiers/workflow#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application). 6. Select the **Create** button to create the new wallet configuration. The authorization endpoint configured in the example above (`mdoc-openid4vp://`) is the default OID4VP scheme. While this is technically redundant, we chose to include this step to explain how to adjust this configuration for wallet applications using different schemes. More information on applying different URI schemes and the resulting user experience can be found in the [workflow page](/docs/verification/remote-web-verifiers/workflow#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application). Make the following request to your MATTR VII tenant to [create a trusted wallet provider configuration](/docs/verification/remote-verification-api-reference/wallet-providers#create-a-wallet-provider): ```http title="Request" POST /v2/presentations/wallet-providers ``` ```json title="Request body" { "name": "MATTR GO Hold", // [!code focus] "openid4vpConfiguration": { "authorizationEndpoint": "mdoc-openid4vp://" // [!code focus] } } ``` * `name` : Unique name to identify this trusted wallet provider. * `authorizationEndpoint` : URI scheme that will be used to invoke the wallet application. More information on applying different URI schemes and the resulting user experience can be found in the [workflow page](/docs/verification/remote-web-verifiers/workflow#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application). The `authorizationEndpoint` configured in the example above (`mdoc-openid4vp://`) is the default OID4VP scheme. While this is technically redundant, we chose to include this step to explain how to configure this endpoint for wallet application using different schemes. More information on applying different URI schemes and the resulting user experience can be found in the [workflow page](/docs/verification/remote-web-verifiers/workflow#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application). *Response* ```json title="Response body" { "id": "99890c34-e4b7-4a23-84d6-e5de57114c00", // [!code focus] "name": "MATTR GO Hold", "openid4vpConfiguration": { "authorizationEndpoint": "mdoc-openid4vp://" } } ``` * `id` : We will use this value later to indicate this is the wallet the web application expects to receive mDocs from. ### Configure a trusted issuer [#configure-a-trusted-issuer] You must configure trusted issuers on your MATTR VII verifier tenant, as presented mDocs will only be verified if they had been issued by a trusted issuer. This is achieved by providing the PEM certificate of the IACA used by these issuers to sign mDocs. In production environments this certificate can be retrieved in one of two ways: * The issuer can provide it directly to the verifier out of band. * The verifier can retrieve it from the issuer's metadata: 1. Make a GET request to the issuer's `/.well-known/openid-credential-issuer` endpoint. 2. Retrieve the `mdoc_iacas_uri` element from the response. 3. Make a GET request to that URI to retrieve the IACAs list (for example for a MATTR VII Issuer tenant this URI would be `{tenant_url}/v1/openid/iacas`). 1. Expand the **Credential Verification** section in the left-hand navigation panel. 2. Select **Trusted issuers**. 3. Select the **Create new** button. 4. Use the *Certificate PEM file* to upload the [following file](http://files.mattr.global/learn/montcliff-iaca.pem.zip) (ensure you extract the ZIP file and upload the .pem file itself). This is the IACA certificate that identifies the [MATTR Labs demo issuer](https://montcliff-dmv.mattrlabs.com/dashboard) which issues the credential referenced in the [prerequisites](#assets) section. 5. Select the **Add** button to add the new trusted issuer. This completes the configuration of the MATTR VII verifier tenant. In the next step, you will build a simple application that you can run locally to test the verification workflow and ensure everything is functioning as expected. Make the following request to your MATTR VII tenant to [configure a truster issuer](/docs/verification/remote-verification-api-reference/trusted-issuers#create-a-trusted-issuer): ```http title="Request" POST /v2/credentials/mobile/trusted-issuers ``` ```json title="Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\nMIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG\nEwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew\nHhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp\nMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq\nhkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp\ndB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud\nEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq\n232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu\nbWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp\nZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp\nbGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny\nbDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI\nSNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6\n-----END CERTIFICATE-----" } ``` * `certificatePem` : This is the IACA identifying the [MATTR Labs demo issuer](https://montcliff-dmv.mattrlabs.com/dashboard) which issues the credential referenced in the [Testing device prerequisite](#testing-device). If you intend to test this tutorial with a credential different than the one recommended in our [Testing device prerequisite](#testing-device), replace the `certificatePem` value with your own issuer's IACA. *Response* A successful `201` response indicates that this issuer's certificate was added to your MATTR VII tenant's trusted issuer's list. This means that mDocs that use this IACA as their root certificate can be trusted and verified. ## Part 2: Build a web application with mDocs verification capabilities [#part-2-build-a-web-application-with-mdocs-verification-capabilities] In this part of the tutorial, the verification result is passed directly from your MATTR VII tenant to your front-end application. This “front-channel” approach is designed for simplicity—it’s ideal for quick proofs of concept (POCs), experiments, or early-stage integrations where you want to see results quickly without needing a backend in place. However, for more realistic POCs and any production-ready implementation, you’ll want to use a “back-channel” approach. In this model, your backend retrieves and verifies the result directly, allowing it to validate the original challenge and securely bind the verification outcome to a user session in your own system. This helps ensure the integrity of the verification flow, prevents tampering or spoofed responses, and allows your backend to maintain full trust in the result. In the next part of the tutorial, we’ll show you how to extend your implementation with a backend service that securely retrieves verification results and ties them to user sessions in your own environment. Now that the MATTR VII verifier tenant is properly configured, you can proceed with the steps required to embed verification capabilities into your web application: 1. [Setup your environment](#setup-your-environment): Setup the required infrastructure for your web application. 2. [Update the domain and redirect URI](#update-the-domain-and-redirect-uri): Once your tunneling service is up and running you can update the placeholder values in the MATTR VII verifier application configuration. 3. [Initialize the SDK](#initialize-sdk): So that the SDK functions are available in your web application. 4. [Create a credential query](#create-credential-request): Define what information is required for verification and how to handle the interaction with the user. 5. [Handle verification results](#handle-verification-results): Create the logic that enables your web application to retrieve verification results in different workflows. ### Setup your environment [#setup-your-environment] 1. Open your terminal and create a new NextJS default project: ```bash title="BASH" npx create-next-app@latest --src-dir --yes ``` 2. Approve installing any required packages. 3. Insert a name for your project (e.g. `my-verifier-web-application`). 4. Open the project app's folder: ```bash title="BASH" cd my-verifier-web-application ``` 5. Install the SDK: ```bash title="BASH" npm install @mattrglobal/verifier-sdk-web ``` 6. Open the `src/app/page.tsx` file in your preferred code editor and replace all existing code with the following: ```tsx title="tsx" "use client"; // Step 3.2 - Add dependencies export default function Home() { // Step 5.1 - Store results state // Step 6.1 - Create createRequest function // Step 6.3 - Create retrieveResults function // Step 4.1 - Create requestCredentials function // Step 5.4 - Handle same-device redirect // Step 3.3 - Initialize the SDK // Step 5.5 - Add effect to handle redirect return (

mDocs Online Verification Tutorial

{/* Step 4.3 - Add request credentials button */} {/* Step 5.6 - Render results */}
); } ``` This will serve as the basic structure for your application. We will copy and paste different code snippets into specific locations to achieve the different functionalities. These locations are indicated by comments that reference the corresponding tutorial step (e.g. `// Step 3.2 - Add dependencies`). We recommend copying and pasting the comment text to easily locate it in the code. 7. Run the project: ```bash title="BASH" npm run dev ``` This will run the app and make it available at [http://localhost:3000](http://localhost:3000) (or the next available port if 3000 is already used). 8. Start an ngrok tunnel in a new terminal window: ```bash title="BASH" ngrok http http://localhost:3000 ``` If your web application is running on a port different than 3000, use that value in the ngrok command above. Your terminal window should show the tunnel details: ngrok tunnel 9. Make note of the `Forwarding` value, we will use it in the next step.
### Update the domain and redirect URI [#update-the-domain-and-redirect-uri] With the tunneling service now active, update the MATTR VII verifier application configuration to include the domain and redirect URI details. 1. Expand the **Credential Verification** section in the left-hand navigation panel. 2. Select **Applications**. 3. Select the verifier application configuration you created in the [previous step](#create-a-verifier-application-configuration). 4. Update the *Allowed domains* text box to include the `Forwarding` URL from the ngrok tunnel, removing the `https://` prefix. 5. Update the *Redirect URIs* text box to include the `Forwarding` URL from the ngrok tunnel. 6. Select the **Update** button to update the application configuration. The `Forwarding` URL is a temporary URL that will be used to test the web application. It will change every time you restart the ngrok tunnel, so you will need to repeat this step each time you restart the tunnel. Make the following request to your MATTR VII tenant to update the verifier application configuration with the tunneling service details: ```http title="Request" PUT /v2/presentations/applications/{applicationId} ``` * `applicationId` : Replace with the `id` from the response returned when you [created the verifier application configuration](#create-a-verifier-application-configuration). ```json title="Request body" { "name": "My Verifier Web Application", "type": "web", "domain": "your-ngrok-subdomain.ngrok.io", // [!code focus] "openid4vpConfiguration": { "supportedModes": "all", "redirectUris": ["https://your-ngrok-subdomain.ngrok.io"], // [!code focus] "display": { "logoImage": { "url": "https://static.mattr.global/logos/mattr/s/gFC.svg", "altText": "MATTR Logo image" }, "headerText": "Share your information.", "bodyText": "Please scan the QR code to provide information required for completing this interaction." } }, "resultAvailableInFrontChannel": true } ``` * `domain` : Update this value to include the `Forwarding` URL from the ngrok tunnel, removing the `https://` prefix. * `redirectUris` : Update this value to include the `Forwarding` URL from the ngrok tunnel. ### Initialize SDK [#initialize-sdk] 1. Create a new file named `.env.local` in the projects root folder and add the following code to add your tenant's URL to the project's variables: ```tsx title=".env.local" NEXT_PUBLIC_TENANT_URL=https://learn.vii.au01.mattr.global NEXT_PUBLIC_APPLICATION_ID=0eaa8074-8cc4-41ec-9e42-072d36e2acb0 NEXT_PUBLIC_WALLET_PROVIDER_ID=99890c34-e4b7-4a23-84d6-e5de57114c00 ``` * `NEXT_PUBLIC_TENANT_URL` : Replace the parameter value with your [`tenant_url`](/docs/platform-management/portal#getting-started). * `NEXT_PUBLIC_APPLICATION_ID` : Replace the parameter value with the `id` from the response returned when you [created the verifier application configuration](#create-a-verifier-application-configuration). * `NEXT_PUBLIC_WALLET_PROVIDER_ID` : Replace the parameter value with the `id` from the response returned when you [created a wallet configuration](#create-a-supported-wallet-configuration). 2. Copy and paste the following code under the `Step 3.2 - Add dependencies` comment to add required dependencies to your web application: ```tsx title="src/app/page.tsx" import * as MATTRVerifierSDK from "@mattrglobal/verifier-sdk-web"; import { useCallback, useEffect, useState } from "react"; ``` 3. Copy and paste the following code under the `Step 3.3 - Initialize the SDK` comment to create a new function that will be used by your web application to initialize the SDK: ```tsx title="src/app/page.tsx" useEffect(() => { MATTRVerifierSDK.initialize({ apiBaseUrl: process.env.NEXT_PUBLIC_TENANT_URL as string, applicationId: process.env.NEXT_PUBLIC_APPLICATION_ID as string, }); }, []); ``` The function calls the SDK's [initialize](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/functions/initialize.html) function that makes the SDK methods available in your web application while defining the following parameters: * `apiBaseUrl`: This value is retrieved from the `NEXT_PUBLIC_TENANT_URL` project variable defined above. It indicates to the Verifier Web SDK what MATTR VII tenant to interact with in order to verify presented mDocs. * `applicationId` : This value is retrieved from the `NEXT_PUBLIC_APPLICATION_ID` project variable defined above. This will be used by the MATTR VII tenant to identify verification requests from your web application. ### Create credential request [#create-credential-request] Now that we have a function and a UI in place, we need to define what the function does by creating a credential request. This request will define what information is requested from the user, and how to interact with the MATTR VII verifier tenant. 1. Copy and paste the following code under the `Step 4.1 - Create requestCredential function` comment to create a new `requestCredentials` function: ```tsx title="src/app/page.tsx" const requestCredentials = useCallback(async () => { const credentialQuery = { profile: MATTRVerifierSDK.OpenidPresentationCredentialProfileSupported.MOBILE, docType: "org.iso.18013.5.1.mDL", nameSpaces: { "org.iso.18013.5.1": { age_over_18: {}, given_name: {}, family_name: {}, portrait: {}, }, }, } as MATTRVerifierSDK.CredentialQuery; // Step 4.2 - Add credential request // Step 5.2 - Retrieve cross-device verification // Step 5.3 - Add setResults }, []); ``` The function includes a `credentialQuery` variable that defines what information is requested from the user for verification. This example will request the `age_over_18`, `given_name`, `family_name` and `portrait` claims from a credential of profile `mobile` and docType `org.iso.18013.5.1.mDL`, under the `org.iso.18013.5.1` namespace. Presenting a credential with different claims, profile, docType or namespace will fail verification. 2. Copy and paste the following code under the `Step 4.2 - Add credential request` comment so that the new `requestCredentials` function calls the SDK's [`requestCredentials`](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/functions/requestCredentials.html) function, passing the `credentialQuery` variable as a parameter: ```tsx title="src/app/page.tsx" const options: MATTRVerifierSDK.RequestCredentialsOptions = { credentialQuery: [credentialQuery], // Step 6.2 - Use challenge from backend challenge: MATTRVerifierSDK.utils.generateChallenge(), openid4vpConfiguration: { redirectUri: window.location.origin, walletProviderId: process.env.NEXT_PUBLIC_WALLET_PROVIDER_ID, }, }; const results = await MATTRVerifierSDK.requestCredentials(options); ``` * `options` : This object defines the required parameters for calling the SDK's [`requestCredentials`](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/functions/requestCredentials.html) function. It is passed as a parameter to the function when calling it. * `credentialQuery` : Defines what information is requested from the user for verification. In our example we are using the credential query created in the previous step. * `challenge` : Unique challenge generated by the SDK to identify this specific presentation session. We will use it in [part 3](#part-3-integrate-a-backend-into-your-remote-verification-workflow) of the tutorial. * `openid4vpConfiguration`: This object defines the configuration necessary for an OID4VP presentation flow: * `redirectUri` : Indicates that the user will be redirected to the same web application screen when completing a same-device workflow. It can be any other path on your application domain, as long as it is handled by the application. Must be on the same domain where the interaction started. See [the workflow documentation](/docs/verification/remote-web-verifiers/workflow#the-mattr-vii-verifier-tenant-returns-the-verification-results) for more information. * `walletProviderId` : This value is retrieved from the `NEXT_PUBLIC_WALLET_PROVIDER_ID` project variable defined above. 3. Copy and paste the following code under the `Step 4.3 - Add request credentials button` comment to create a new button that the user will use to interact with the web application: ```tsx title="src/app/page.tsx" ``` Once the user selects this button, the web application will trigger the verification workflow by calling the `requestCredentials` function created above: * This function calls the SDK's [`requestCredentials`](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/functions/requestCredentials.html) method which starts a presentation session with the configured MATTR VII session. * Once a session is established, the MATTR VII verifier tenant will interact with the user's wallet application to request and verify the details provided in the query. * When the verification process is complete, the MATTR VII verifier tenant will return the verification results to your web application. ### Handle verification results [#handle-verification-results] 1. Copy and paste the following code under the `Step 5.1 - Store results state` comment to create a variable that will store the verification state: ```tsx title="src/app/page.tsx" const [results, setResults] = useState(null); ``` 2. Copy and paste the following code under the `Step 5.2 - Retrieve cross-device verification results` comment to retrieve verification results in cross-device workflows: ```tsx title="src/app/page.tsx" if (results.isOk()) { // Step 6.4 - Call retrieveResults function in cross-device workflow setResults( results.value.result as MATTRVerifierSDK.PresentationSessionResult, ); } else { alert(`Error retrieving results: ${results.error.message}`); } ``` 3. Add `setResults` in the square brackets under the `// Step 5.3 - Add setResults` to set the `results` state variable once results are available. Your code should look as follows: ```tsx title="src/app/page.tsx" }, [setResults]) ``` 4. Copy and paste the following code under the `Step 5.4 - Handle same-device redirect` comment to create a new `handleRedirect` function: ```tsx title="src/app/page.tsx" const handleRedirect = useCallback(async () => { const results = await MATTRVerifierSDK.handleRedirectCallback(); if (results.isOk()) { // Step 6.6 - Call retrieveResults function on same-device redirect if (results.value.result) { setResults(results.value.result); } } else { alert(`Error retrieving results: ${results.error.message}`); return; } // Step 6.7 - Replace setResults function with retrieveResults function }, [setResults]); ``` This function calls the SDK's [handleRedirectCallback](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/functions/handleRedirectCallback.html) function, which returns the verification results as the user is redirected following a same-device presentation workflow. These results (`results.value.result`) are then assigned to the `results` state variable by `setResults`. 5. Copy and paste the following code under the `Step 5.5 - Add effect to handle redirect` comment to add an effect that calls the `handleRedirect` function when the web application is refreshed with a hash, indicating a redirect after completing a same-device presentation workflow: ```tsx title="src/app/page.tsx" useEffect(() => { if (window.location.hash) { handleRedirect(); } }, [handleRedirect]); ``` 6. Copy and paste the following code under the `Step 5.6 - Render results` comment to retrieve properties of the `results` object once it is available, and display them to the user as part of the interaction: ```tsx title="src/app/page.tsx"

Results

{ !results && "No results available" } { results && "credentials" in results && (
Verification Result
{results.credentials?.[0].verificationResult.verified ? "Verified" : "Not Verified"}
Valid Until
{results.credentials?.[0].validityInfo?.validUntil}
Issuer
{results.credentials?.[0].issuerInfo?.commonName}
Given Name
{(results.credentials?.[0].claims?.["org.iso.18013.5.1"].given_name .value as string) ?? ""}
Age over 18
{results.credentials?.[0].claims?.["org.iso.18013.5.1"].age_over_18 .value ? "Yes" : "No"}
) } { results && "error" in results && (
{results.error.type}
{results.error.message}
) } ``` In this example when verification is successful (`"credentials" in results`) the app retrieves and displays the following properties of the first credential (`credentials?.[0]`) of the `results` object: * `verificationResult.verified` : Indicates whether or not the mDoc was verified. * `validityInfo.validUntil` : Indicates the mDoc expiry date. * `issuerInfo.commonName` : Indicates the issuer's name. * `claims?.['org.iso.18013.5.1'].given_name` : Indicates the holder's given name. * `claims?.['org.iso.18013.5.1'].age_over_18.value` : Indicates whether or not the holder is over 18. When verification fails (`"error" in results`) the app retrieves and displays error details (`error.type` and `error.message`) from the `results` response. Your web application will now continue the interaction based on the workflow type: * For cross-device workflows, the web application will display the verification results in the desktop browser where the interaction started. The user can then continue the interaction on this desktop browser. * For same-device workflows, the MATTR VII verifier tenant will redirect the user back to the web application in their mobile browser, where verification will be displayed. The user can then continue the interaction on this mobile browser. Congratulations, your web application is now ready to verify mDocs. Let's test what we've built!
### Test the front channel interaction [#test-the-front-channel-interaction] 1. Open your **ngrok forwarding URL** in a browser on a mobile device where the [MATTR GO Hold example app](/docs/holding/go-hold/getting-started) is installed. 2. Select the **Request Credential** button. 3. Select **Allow** to open the GO Hold app. 4. The app should launch and display what information is requested for verification. 5. Select **Start**. 6. Select **Confirm** to share a credential which includes the requested information (claim one [here](/docs/holding/go-hold/getting-started#claim-a-credential) if you haven't done so already). 7. The web application should display the verification results as a string. The result should look something like this: 1. In a desktop browser, open your public **ngrok forwarding URL** (Do not use localhost as the cross-device flow requires a publicly accessible URL). 2. Select the **Request Credential** button. A QR code will be displayed. 3. Open the camera on a mobile device where MATTR GO Hold is installed and scan the QR code. 4. Confirm opening the QR code with the GO Hold app. 5. The app should launch and display what information is requested for verification. 6. Select **Start**. 7. Select **Confirm** to share a credential which includes the requested information (claim one [here](/docs/holding/go-hold/getting-started#claim-a-credential) if you haven't done so already). 8. Back on your desktop browser you the web application should display the verification results as a string. The result should look something like this: If your goal was just to see mDocs online verification in action, there's no need to proceed to the next step of the tutorial. However, production solutions often integrate a backend, and if you're curious about how that works, feel free to continue to the next step—it's optional and won't change the overall workflow, just what’s happening behind the scenes.
## Part 3: Integrate a backend into your online verification workflow [#part-3-integrate-a-backend-into-your-online-verification-workflow] Your online verification workflow is now working well using a front-channel flow, where results are passed directly from the MATTR VII tenant to your front-end application. This approach is ideal for quick and easy POCs, experiments, or demonstrations where you want to get up and running fast. To make your solution more secure and representative of real-world integrations, it’s recommended to introduce a backend into the workflow. This “back-channel” approach allows your backend to retrieve and validate verification results directly from your MATTR VII tenant. It also enables you to correlate verification outcomes with user sessions in your own system—something that’s essential for realistic POCs and production deployments. In this part of the tutorial, you’ll configure a simple backend service that securely retrieves verification results from your MATTR VII tenant, validates them, and then passes the verified outcome to your front-end application. For more details on how the end-to-end workflow operates, refer to the [detailed workflow documentation](/docs/verification/remote-web-verifiers/workflow#the-mattr-vii-verifier-tenant-returns-the-verification-results). ### Update verifier application configuration [#update-verifier-application-configuration] The first thing you need to do is to make sure verification results are not returned directly to the web application's front channel. This setting is part of your MATTR VII verifier application configuration. 1. Expand the **Credential Verification** section in the left-hand navigation panel. 2. Select **Applications**. 3. Select the verifier application configuration you created in the [previous step](#create-a-verifier-application-configuration). 4. Use the *Verification results return method* radio button to select **Must be retrieved and validated by backend**. 5. Select the **Update** button to update the application configuration. This setting is only available when the application type is set to **Web**. Make the following request to your MATTR VII tenant to update the verifier application configuration so that verification results are not returned directly to the web application's front channel: ```http title="Request" PUT /v2/presentations/applications/{applicationId} ``` * `applicationId` : Replace with the `id` from the response returned when you [created the verifier application configuration](#create-a-verifier-application-configuration). ```json title="Request body" { "name": "My Verifier Web Application", "type": "web", "domain": "your-ngrok-subdomain.ngrok.io", "openid4vpConfiguration": { "supportedModes": "all", "redirectUris": ["https://your-ngrok-subdomain.ngrok.io"], "display": { "logoImage": { "url": "https://static.mattr.global/logos/mattr/s/gFC.svg", "altText": "MATTR Logo image" }, "headerText": "Share your information.", "bodyText": "Please scan the QR code to provide information required for completing this interaction." } }, "resultAvailableInFrontChannel": false // [!code focus] } ``` * `resultAvailableInFrontChannel` : Set this value to `false` to ensure that verification results are not returned directly to the web application's front channel. This is the only change required in the request body. ### Add your MATTR VII login details [#add-your-mattr-vii-login-details] Your backend will need to make a request to a protected MATTR VII endpoint to retrieve the verification results. To enable this, we must provide it with your [MATTR VII tenant login details](/docs/platform-management/portal#getting-started). 1. Add the following code to your `.env.local` file to add your [MATTR VII tenant login details](/docs/platform-management/portal#getting-started): ```tsx title=".env.local" MATTR_CLIENT_ID=ZAGvD******************************** MATTR_CLIENT_SECRET=uc8NM**************************************** ``` * `MATTR_CLIENT_ID` : Replace with your `client_id` value. * `MATTR_CLIENT_SECRET` : Replace with your `client_secret` value. ### Create a session and associate a challenge [#create-a-session-and-associate-a-challenge] The web application backend needs to generate a unique challenge in the context of an existing user session. In a typical application, the user would likely already interact with the application inside an active session, and often that session is tracked using a cookie. In this tutorial we are mimicking this by setting a random value as the user session ID. We are also creating a random `challenge` and storing it inside a cookie to ensure that the presentation results we are retrieving belong to the user in the current user session. 1. Create a new file at `src/app/api/request/route.ts` with the following content: ```tsx title="src/app/api/request/route.ts" import { cookies } from "next/headers"; export async function POST() { const sessionId = crypto.randomUUID(); const challenge = crypto.randomUUID(); const cookieStore = await cookies(); cookieStore.set("session_id", sessionId, { httpOnly: true, secure: true, sameSite: true, }); cookieStore.set("challenge", challenge, { httpOnly: true, secure: true, sameSite: true, }); return Response.json({ challenge }, { status: 201 }); } ``` While it's not strictly necessary to keep the `challenge` secret, you might want to only use a single encrypted session cookie to not leak any information about your session management. If you use a database to store the sessions state, you could use that database to store the `challenge` against the user session. ### Retrieve verification results [#retrieve-verification-results] Next we need to create an API route that will make a request to the MATTR VII verifier tenant and retrieve the results of a specific presentation session based on its ID. 1. Create a new file at `src/app/api/results/[id]/route.ts` with the following content: ```tsx title="src/app/api/results/[id]/route.ts" import { cookies } from "next/headers"; async function getToken(tenantUrl: string) { const region = tenantUrl.split(".")[2]; const response = await fetch( `https://auth.${region}.mattr.global/oauth/token`, { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ client_id: process.env.MATTR_CLIENT_ID, client_secret: process.env.MATTR_CLIENT_SECRET, audience: tenantUrl, grant_type: "client_credentials", }), }, ); const { access_token } = await response.json(); return access_token; } export async function GET( _: Request, { params }: { params: Promise<{ id: string }> }, ) { const tenantUrl = process.env.NEXT_PUBLIC_TENANT_URL; if (!tenantUrl) { return Response.json({ error: "No tenant URL configured" }); } const id = (await params).id; const cookiesStore = await cookies(); if (!cookiesStore.get("session_id")) { return Response.json( { error: "No valid session for user" }, { status: 401 }, ); } const response = await fetch( `${tenantUrl}/v2/presentations/sessions/${id}/result`, { headers: { authorization: `Bearer ${await getToken(tenantUrl)}`, }, }, ); const results = await response.json(); if (results.challenge !== cookiesStore.get("challenge")?.value) { return Response.json({ error: "Challenge does not match" }); } return Response.json({ results }); } ``` The `getToken` function first uses project variables to retrieve a MATTR VII access token, as the results are retrieved from a protected endpoint. The route must be called with a dynamic `id` route parameter. This is the unique identifier of the presentation session. It is used in the authenticated request to the `/v2/presentations/sessions/${id}/result` endpoint to retrieve the corresponding verification results. Your backend must then validate the returned results. The exact implementation would depend on your user session management, but you should always ensure that: * The request to retrieve the results comes from an active user session (as per its `session_id`). * The `challenge` included in the response from the MATTR VII tenant matches the `challenge` you have stored for that active user session. ### Adjust front channel to retrieve results via backend [#adjust-front-channel-to-retrieve-results-via-backend] Now that the backend is configured to retrieve the verification results, we need to adjust the front channel web application so that it can retrieve these results from the backend. 1. Return to your `src/app/page.tsx` file and copy and paste the following code under the `Step 6.1 - Create createRequest function` comment. ```tsx title="src/app/page.tsx" async function createRequest() { const response = await fetch("/api/request", { method: "POST" }); const { challenge } = await response.json(); return challenge; } ``` 2. Replace the `challenge` assignment under the `Step 6.2 - Use challenge from backend` comment with the following code to call the `createRequest` function to generate a challenge: ```tsx title="src/app/page.tsx" challenge: await createRequest(), ``` 3. Add the following code under the `Step 6.3 - Create retrieveResults function` comment to create a new function that retrieves the verification results: ```tsx title="src/app/page.tsx" const retrieveResults = useCallback( async (id: string) => { const response = await fetch(`api/results/${id}`); const { results } = await response.json(); setResults(results as MATTRVerifierSDK.PresentationSessionResult); }, [setResults], ); ``` This function uses the API route created [above](#retrieve-verification-results) to retrieve the verification results and use `setResults` to assign them to the `results` state variable 4. Replace the `setResults` call under the `Step 6.4 - Call retrieveResults function in cross-device workflow` with the following code to call the new `retrieveResults` function in the callback of a cross-device workflow: ```tsx title="src/app/page.tsx" retrieveResults(results.value.sessionId); ``` 5. Replace the `setResults` function in the square brackets under the `// Step 5.3 - Add setResults` comment with the `retrieveResults` function. Your code should look like this: ```tsx title="src/app/page.tsx" }, [retrieveResults]) // requestCredentials dependency array ``` 6. Replace the whole conditional around the `setResults` call under the `Step 6.6 - Call retrieveResults function on same-device redirect` with the following code to call the new `retrieveResults` function when the user is redirected to the app following a same-device workflow: ```tsx title="src/app/page.tsx" retrieveResults(results.value.sessionId); ``` 7. Replace the `setResults` function in the brackets under the `// Step 6.7 - Replace setResults function with retrieveResults function` comment with the `retrieveResults` function. The line should look like this: ```tsx title="src/app/page.tsx" }, [retrieveResults]) ``` Your web application now has an integrated backend that can validate verification results before passing them to the front channel, making the solution more secure. ### Test the backend interaction [#test-the-backend-interaction] Let's test to ensure everything is working as expected. 1. Repeat the steps in the [front channel testing](#test-the-front-channel-interaction) step above. 2. The interaction should look the same, but behind the scenes it will actually be handled by your backend. ## What's next? [#whats-next] * You can adjust the app built in this tutorial to request and verify credentials using the Digital Credentials API, as detailed in the [DC API Guide](/docs/verification/remote-web-verifiers/dc-api/guide). * This project was run and tested locally using tunneling services. To build similar online verification capabilities into a production web application, all you need to do is replace the `domain` and `redirectUri` with the corresponding values of your production web application. * This project was created to showcase happy paths. It is recommended to review our comprehensive [SDK reference documentation](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/index.html) to learn more about available functions and classes. * As always, feel free to [reach out](mailto:dev-support@mattr.global) if you have any questions or encounter any issues we can help with. # mDocs remote web app verification - OID4VP Workflow URL: /docs/verification/remote-web-verifiers/workflow mDocs are digital credentials based on the ISO/IEC [18013-5](https://www.iso.org/standard/69084.html) standard and [18013-7](https://www.iso.org/standard/91154.html) technical specification, designed to be stored on a holder’s mobile device and support either in-person or remote verification workflows. The purpose of this page is to describe the end-to-end remote web app verification workflow, where a user interacts with a web application to present an mDoc stored on their mobile device. ## Prerequisites [#prerequisites] We recommend you make yourself familiar with the following concepts to support your understanding of the implementation described in this page: * What is [credential verification](/docs/verification)? * What are [mDocs](/docs/concepts/mdocs)? * What is [remote verification](/docs/verification/remote-overview)? ## Overview [#overview] Remote web app verification of mDocs is based on [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). Let's use a simple example to illustrate how such a workflow might look like: Remote web app verification overview 1. A relying party uses the Verifier Web SDK to embed a remote verification workflow into an existing web application. For example, a bank embeds remote verification as part of the workflow for opening a new bank account on their online portal. 2. When a user interacts with the web application, a matching application installed on their mobile device is invoked to request sharing information held in an mDoc. In our example, the user attempts to open a new bank account in the bank's online portal, and is requested to provide a supporting identity document, such as a Mobile Drivers License (mDL). 3. The user consents to sharing the requested information. In our example, the user has already claimed an mDL as an mDoc into their mobile application in a previous interaction, and can now share that credential to prove their address. 4. The user's mobile application shares the matching mDoc with the MATTR VII tenant configured by the Verifier Web SDK to perform the verification workflow. 5. The MATTR VII tenant performs the required checks and returns the verification results via the Verifier Web SDK to the web application. 6. The user journey continues based on the verification results and the relying party business logic built into the web application. In our example, pending successful verification of the mDoc, the user can proceed to opening a new bank account. ## Detailed workflow [#detailed-workflow] Let's take a closer look at the end-to-end workflow and explain the role of each component: ### The user triggers the workflow [#the-user-triggers-the-workflow] The workflow is triggered when the user interacts with a web application that requires presenting information stored in an mDoc. For example, the user is attempting to open a bank account and is required to provide a supporting identity document, which can be provided by presenting an mDoc stored in an application on their mobile device. In a same-device workflow, the user must begin the interaction in their default web browser. Starting the interaction in a **different browser** or in **incognito mode** will lead to session recognition issues and possible failures. ### The Verifier Web SDK starts a presentation-based verification session [#the-verifier-web-sdk-starts-a-presentation-based-verification-session] The web application uses the Verifier Web SDK to send a request to a MATTR VII verifier tenant to start a remote, presentation-based verification session. This request is based on the Verifier Web SDK configuration that defines: * The credential query: * What credentials are required. * What specific claims are required from these credentials. * What MATTR VII tenant to interact with. * A unique identifier of this web application. * What [protocol](/docs/verification/remote-overview#verification-requests) to use for the interaction. * An optional `state` value, used to correlate the verification session with a record in your own system. See [Correlating verification sessions](/docs/verification/remote-web-verifiers/guides/correlating-verification-sessions). On the MATTR VII side, the tenant is configured to determine how to handle requests from different [applications](/docs/verification/remote-verification-api-reference/verifier-applications#create-a-verifier-application) based on the application identifier provided in the request. ### The MATTR VII verifier tenant responds with a link to invoke a matching application [#the-mattr-vii-verifier-tenant-responds-with-a-link-to-invoke-a-matching-application] The MATTR VII verifier tenant responds with a URI, referred to as the *Authorization endpoint* in OID4VP. This endpoint is configured when you [create a trusted wallet provider](/docs/verification/remote-verification-api-reference/wallet-providers#create-a-wallet-provider) and defines the URI to invoke an application based on its unique identifier. As per OID4VP, the Authorization endpoint can be one of the following: * **Default URI scheme**: This is a generic URI scheme defined by the OID4VP specification (`openid-vp://`) that can be handled by any compliant application. * If you are interacting with the link from an app that already recognizes this type of scheme, it can handle the request immediately. * If you are interacting with the link outside of the app (such as tapping a link in a browser or scanning a QR code with the native camera), this will typically prompt the OS to present options of any app that is registered to handle that scheme (on Android), or sometimes it will automatically pick an app without enabling user selection (on iOS). This approach is less secure as **any application** registered to handle this scheme could respond to the request, which might lead to phishing attacks where a malicious app pretends to be a trusted one. * **Custom URI**: The [OAuth 2.0 for Native Apps](https://datatracker.ietf.org/doc/html/rfc8252) specification proposes using more unique custom schemes to make it clearer that the link is intended for a particular app. This uses reverse-domain schemes, such as `com.example.app://`. This works in the same way as the default OID4VP custom scheme, but makes it less likely that another app would be registered to handle the same reverse domain. * **HTTP link**: Also called out in the [OAuth 2.0 for Native Apps](https://datatracker.ietf.org/doc/html/rfc8252) specification, the most secure way to ensure only a specific app can handle a link is to register a domain path to open your app via App Links on Android and Universal Links on iOS. This is an HTTPS URL pointing to your domain, which the operating system verifies is registered to only open a particular app. It provides the smoothest user experience as the OS can reliably open the correct app without presenting choices. HTTP links are recommended by the OID4VP specification as they are more secure. These URIs are verified against the domain owner before launching the application, as the domain must host an association file declaring the app's link. This ensures the provider owns both the app and the domain. Additionally, it is recommended to host a webpage for this URI to allow the user to continue the interaction if the HTTP link fails to invoke the intended application. For example, the user might not have the application installed and in this case the page should include a link to enable them to download the app. HTTP links require domain ownership and proper configuration of verification files on your web server. More information can be found in the following resources: * [iOS - Universal Links](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) * [Android - App Links](https://developer.android.com/training/app-links/verify-site-associations) To use a custom URI scheme or App Link/Universal Link, the *Authorization endpoint* must be properly configured on the MATTR VII verifier tenant when creating a trusted wallet provider, for example: * Custom URI scheme: `com.example.app://openid-vp` (where `com.example.app` is the app's reverse domain and `://openid-vp` is the app path that can handle the request) * HTTP link: `https://example.com/openid-vp` (where `example.com` is a domain registered to open the specific app and configured with proper verification files) The verifier's website can also include a UI element to allow users to select the app they want to use for presenting credentials. Based on this selection, the Verifier Web SDK will identify the application and send the corresponding identifier to the MATTR VII verifier tenant. The tenant will then return the associated URI (Authorization endpoint). Regardless of which URI scheme approach you choose, holder apps you expect to verify credentials from must be properly registered with the operating system to handle these schemes. See the following resources for platform-specific implementation guides: * [Apple Docs - Defining a Custom URL Scheme for Your App](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) * [Android Docs - Create Deep Links to App Content](https://developer.android.com/training/app-links/deep-linking) ### The link is used to invoke a compliant application [#the-link-is-used-to-invoke-a-compliant-application] The link is used by the Verifier Web SDK to invoke a compliant mobile application based on the workflow type: * **Same-device flow**: The link is used to redirect the user to a compliant application on the same device. When using a Custom URI a dialogue box appears on the mobile device, prompting them to launch a matching application (this dialogue box is not displayed for HTTP links). * **Cross-device flow**: The link is rendered as a QR code and displayed in an embedded iframe. The user must then use a different mobile device to scan the QR code and invoke a compliant mobile application. The displayed QR code is automatically refreshed every 60 seconds to minimize risk to session fixation attacks, where the attacker is trying to induce the user to scan a QR code and complete a verification flow on their behalf. Some of the iframe display properties can be adjusted as part of the verifier application configuration on the MATTR VII tenant, to enable presenting different iframes to different verifier applications. The MATTR VII verifier tenant can be configured to support different remote verification workflows (e.g. same-device and/or cross-device) when interacting with different verifier applications. ### The mobile application makes a request to retrieve a request object [#the-mobile-application-makes-a-request-to-retrieve-a-request-object] The invoked mobile application can now use the link to retrieve the request object from the MATTR VII verifier tenant. This is a presentation request that defines what information is requested by the verifier and for what purpose. In [OAuth 2.0 terminology](https://datatracker.ietf.org/doc/html/rfc6749#section-1.1) this is referred to as an Authorization request, made by the MATTR VII verifier tenant which acts as a **Client**. The request is sent to the mobile application which acts as an **Authorization server**, as it controls access to protected resources, in this case stored credentials. ### The MATTR VII tenant returns the request object [#the-mattr-vii-tenant-returns-the-request-object] The MATTR VII verifier tenant generates the request object, signs it with its verifier certificate and sends it as a response to the mobile application's request. The mobile application will handle the received request object and perform the following: * Establish trust with the relying party in one of the following methods: * **Certificate trust model**: The mobile application compares the certificate used to sign the request object against its list of trusted verifiers. This would be the [`certificatePem`](/docs/verification/certificates/api-reference/verifier-root-ca-certificates#create-a-verifier-root-ca-certificate) element of the verifier root CA, and must be provided by the verifier to the holder out of band. * **Domain trust model**: The mobile application retrieves the relying party metadata by making a request to the `/.well-known/oauth-client` endpoint of the domain the request object is coming from. By default this would be the verifier's [`tenant_url`](/docs/platform-management/portal#getting-started). When using a [Custom domain](/docs/platform-management/custom-domain-overview), you must configure a [redirect](/docs/platform-management/custom-domain-guide#create-redirects-for-required-assets) for that path from your custom domain to your MATTR VII verifier tenant. * Gather credentials matching the information provided in the request object. * Display this information to the holder and ask for consent to sharing it with the relying party. The MATTR VII verifier tenant uses a [verifier root CA certificate](/docs/verification/certificates/api-reference/verifier-root-ca-certificates#create-a-verifier-root-ca-certificate) to identify itself when interacting with wallet applications. ### The mobile application sends an authorization response [#the-mobile-application-sends-an-authorization-response] Once the user consents to sharing the information, the mobile application generates an authorization response, which includes a signed verifiable presentation of matching credentials contained in a `vp_token` parameter. The authorization response is encrypted and sent to the MATTR VII verifier tenant. Upon receiving the authorization response, the MATTR VII verifier tenant would decrypt it and [verify](/docs/verification/remote-overview) the contained verifiable presentation. This can include checking whether the certificate used to sign the credential matches an existing Issuer in the MATTR VII tenant trusted issuers list. ### The MATTR VII verifier tenant returns the verification results [#the-mattr-vii-verifier-tenant-returns-the-verification-results] The MATTR VII tenant returns the [verification](/docs/verification/remote-overview) results based on the verification workflow: * **Same-device flow**: the MATTR VII tenant responds with a redirect URI which is used by the mobile application to redirect the user back to continue the interaction with the web application. This redirect opens a new tab in the user's mobile browser, leaving the original tab (where the [interaction began](#the-user-triggers-the-workflow)) still open. * This redirect URI must be on the same domain as the one used to start the interaction with the web application, to ensure session recognition. For example, if the user started the interaction on `https://maggies.mattrlabs.com`, the redirect URI must also be on the `maggies.mattrlabs.com` domain, such as `https://maggies.mattrlabs.com/verification-complete`. * The redirect URI can be configured per verifier application, enabling redirecting users to different targets based on the verifier application they are interacting with. * The redirect URI will be handled by the device *default* web browser. This means that the user must start their journey in their default web browser as well, otherwise it will be recognized as a different session and result in a failure. * It is recommended to advice users to refrain from starting the workflow in private/incognito mode, as browser limitations might lead to unexpected behavior and possible failures. * **Cross-device flow**: The Verifier Web SDK continuously checks for verification results. Once they are received they are passed to the web application and the cross-device workflow modal is closed. When the web application has a backend, the MATTR VII verifier tenant can be configured to only send the results to the backend rather than to the frontend directly. This enhances security as described in the following flow: 1. The backend creates a unique challenge when the session is established. 2. The web application passes the unique challenge with the request to start a presentation session. 3. The web application is notified when the MATTR VII tenant had completed verification. 4. The web application passes the session ID to the backend. 5. The backend makes a [request](/docs/verification/remote-verification-api-reference/presentation-sessions#retrieve-a-presentation-session-result) to MATTR VII to retrieve the verification results for that session. 6. The MATTR VII tenant responds with the verification result and the unique challenge. 7. The backend compares the original and the received challenge to ensure the response can be trusted. 8. The backend passes the verification results to the web application. Results availability can be configured per verifier application. ### The web application surfaces verification results [#the-web-application-surfaces-verification-results] The user journey can now proceed based on the web application business logic and the verification results received. In our example, if the presented mDL was verified, the user can proceed to opening a bank account, whereas if verification failed they will receive an error message. #### Understanding verification results [#understanding-verification-results] Verification results indicate whether the presentation succeeded and whether the credentials were verified. Results can fall into two high-level categories: **Presentation failed**: The holder was unable to complete the presentation workflow (e.g., wallet unavailable, session aborted, response errors). **Presentation succeeded**: The holder completed the presentation workflow. Results may include verified credentials, credential errors (requested credentials not provided), claim errors (specific claims that failed verification), or a combination of these outcomes. #### Result delivery [#result-delivery] How your application receives verification results depends on your MATTR VII verifier application configuration: **Front channel** (`resultAvailableInFrontChannel: true`): Results are returned directly to your web application through the Verifier Web SDK. **Back channel** (`resultAvailableInFrontChannel: false`): Your backend retrieves results by calling the MATTR VII tenant's [presentation session result endpoint](/docs/api-reference/platform/mdocs-presentation-sessions/getPresentationResult). Production implementations should use back channel delivery with challenge validation to protect against session replay attacks. For detailed information about result structures, error types, and complete examples, see the [handling verification results guide](/docs/verification/remote-web-verifiers/guides/handling-verification-results). # Android Verifier SDK v6.0.0 Migration Guide URL: /docs/verification/sdks/android-6.0.0-migration-guide ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in MobileCredentialVerifierSDK v6.0.0 for Android, including breaking changes, new features, and migration steps. ## Key Features [#key-features] * **Improved reliability in contactless flows**: Enhanced BLE performance delivers more consistent proximity credential exchanges and faster engagements. * **Stronger cryptography and standards alignment**: Updated COSE algorithms (as per RFC 9864) strengthen cryptographic compatibility and ensure continued compliance with evolving standards. * **Simpler, Decoupled Releases**: The Android Verifier SDK no longer has a shared common module. This allows us to decouple the releases of Holder and Verifier and versions no longer need to match. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency across mobile environments, and improve overall user experience. For a detailed list of changes included in this release, refer to the [SDK Changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/). ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v6.0.0 that require updates to your existing implementation: | # | Element | Change | Impact | | - | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------- | | 1 | `MobileCredentialVerifier.sendProximityPresentationRequest(skipStatusCheck = ...)` | Parameter renamed to `checkStatus = ...` with **inverted semantics** | All call sites using `skipStatusCheck = ...` must be updated. | | 2 | `mattr.global.mobilecredentials.common` | Package path moved to `mattr.global.mobilecredentials.verifier` | All imports must be updated. | ## Bug Fixes [#bug-fixes] * Fixed parsing of status lists that use a bit size other than 2. * Fixed an issue where the Wallet could attempt to start a new session before the previous session had completed. * Resolved BLE retry issues during proximity presentations that resulted in "SDK not ready (Engaging)" errors. * Improved cross-device flow handling in DCM scenarios and resolved result mismatches. * Corrected UI refresh issues following automatic session termination. ## Minimum Requirements [#minimum-requirements] * Android 7 / Nougat / API 24. * The Android Verifier SDK is built using Kotlin 2.0. This adds some intrinsic dependencies into your build tools. * Kotlin 2.0 is supported from AGP version [8.5](https://developer.android.com/build/kotlin-support). * AGP 8.5 is supported from Gradle version [8.7](https://developer.android.com/build/releases/about-agp) and Android Studio Koala [2024.1.1](https://developer.android.com/studio/releases). ## Migration Steps [#migration-steps] ### Rename any references to the `common` package to `verifier` [#rename-any-references-to-the-common-package-to-verifier] The shared `common` module has been removed and bundled into the Android Verifier SDK. The package path has moved from `mattr.global.mobilecredentials.common` to `mattr.global.mobilecredentials.verifier`. Update all imports accordingly: ```diff - import mattr.global.mobilecredentials.common.* + import mattr.global.mobilecredentials.verifier.* ``` This can be done with a global find and replace across your codebase. If you are using both the Holder and Verifier SDKs, you will need to limit your search to the relevant parts of your application. ### Remove the Common dependency from your project [#remove-the-common-dependency-from-your-project] The Common dependency is now bundled into the Verifier SDK. Remove it from your project's `build.gradle` or `build.gradle.kts` file: ```diff dependencies { implementation("global.mattr.mobilecredentials:verifier:6.0.0") - implementation("global.mattr.mobilecredentials:common:5.x.x") } ``` ### Update `sendProximityPresentationRequest` calls [#update-sendproximitypresentationrequest-calls] The `skipStatusCheck` parameter has been renamed to `checkStatus` with inverted semantics. When `checkStatus` is `true` (default), the SDK will verify credential status. When `false`, it will skip status checking. Update all calls accordingly: ```diff - val response = verifier.sendProximityPresentationRequest(request = requests, skipStatusCheck = true) + val response = verifier.sendProximityPresentationRequest(request = requests, checkStatus = false) ``` | Old Parameter | New Parameter | Mapping | | ----------------------------------- | ------------------------------ | ------------------ | | `skipStatusCheck = false` (default) | `checkStatus = true` (default) | No change needed | | `skipStatusCheck = true` | `checkStatus = false` | Invert the boolean | # Android Verifier SDK v7.0.0 Migration Guide URL: /docs/verification/sdks/android-7.0.0-migration-guide Description: A comprehensive guide to migrating to Android Verifier SDK v7.0.0, covering breaking changes, new features, and step-by-step migration instructions. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in the Android Verifier SDK v7.0.0, including breaking changes, new features, and migration steps. This release focuses on strengthening trust between your verifier application and your MATTR VII tenant, improving consistency across platforms, and making verification results more predictable. The headline change is **SDK Tethering**, which becomes **required** in this release: every SDK and app instance is now registered with, and licensed by, your MATTR VII tenant at initialization. Unlike the Holder SDK, where SDK Tethering is optional, **SDK Tethering is required for the Verifier SDK** from v7.0.0. This builds on an existing requirement — remote mobile (app-to-app) verification already required you to supply a `platformConfiguration` so the SDK could reach your MATTR VII tenant to handle the backend verification. That `platformConfiguration` is now mandatory for all initializations and additionally drives SDK Tethering. ## Key Features [#key-features] * **SDK Tethering (required)**: The Android Verifier SDK is now tethered to a MATTR VII tenant, tying each SDK/app instance to your tenant. On first initialization the SDK registers the app instance with the tenant specified in `PlatformConfiguration` and obtains a license; on subsequent initializations the existing license is renewed automatically. This lets you view registered and active app instances directly from your tenant for operational insight, and establishes a remote management channel we expect to extend in future releases (for example, remote syncing of trusted issuer lists and eventing). The SDK uses [Key Attestation](https://source.android.com/docs/security/features/keystore/attestation) during app registration. Network access is required when registration or renewal is performed. * **Cross-platform alignment**: Verification result types and the revocation status list API have been renamed and restructured to align with the iOS Verifier SDK, minimizing divergence for teams maintaining cross-platform applications. * **More predictable session results**: `OnlinePresentationSessionResult` and the revocation status list refresh result are now sealed interfaces with explicit `Success` and `Failure` variants, removing ambiguity from result handling. * **Simpler remote mobile configuration**: The application ID for remote mobile (app-to-app) verification is now taken from `PlatformConfiguration`, so it no longer needs to be passed on every request. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency, and improve overall reliability. ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v7.0.0 that require updates to your existing implementation: | # | Change | Impact | | - | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | SDK Tethering is now required: `PlatformConfiguration` is mandatory on `initialize`, which now also registers the app instance and obtains a license | Always supply a `PlatformConfiguration`. Handle the new `InvalidLicenseException` and `FailedToRegisterException`, which can be thrown by `initialize` and by most SDK APIs. | | 2 | `MobileCredentialVerifier.updateTrustedIssuerStatusLists` renamed to `refreshRevocationStatusLists` | Update all references to the new method name. | | 3 | `MobileCredentialVerifier.getTrustedIssuerStatusListsCacheInfo` renamed to `getRevocationStatusListsCacheInfo` | Update all references to the new method name. | | 4 | `TrustedIssuerStatusListsCacheInfo` renamed to `RevocationStatusListsCacheInfo` | Update all code that constructs or references this type. | | 5 | `UpdateTrustedIssuerStatusListsResult` renamed to `RevocationStatusListsRefreshResult` and converted to a sealed interface; the `.success: Boolean` field is removed | Replace `if (result.success)` with `when`/`is` pattern matching over `Success` and `Failure`. | | 6 | `OnlinePresentationSessionResult` converted to a sealed interface with `Success` and `Failure` variants | Replace field-based branching with `when`/`is` pattern matching. | | 7 | `MobileCredentialResponse.credentials` and `MobileCredentialResponse.credentialErrors` are now required non-nullable `List` fields | Remove null-check branches and `?.` navigation; both arguments must be provided at construction. | | 8 | `applicationId` parameter removed from `requestMobileCredentials` (remote mobile, app-to-app) | Remove the `applicationId` argument and supply it via `PlatformConfiguration` instead. | ## Migration Steps [#migration-steps] ### Create a verifier application on your MATTR VII tenant [#create-a-verifier-application-on-your-mattr-vii-tenant] SDK Tethering requires a verifier application configured on the MATTR VII tenant your SDK connects to. If you already use remote mobile (app-to-app) verification you will have created one; the same application is reused for tethering. If you have not, create one now. To register your Android application, make a request to create a verifier application: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584b6f6a892d356899fb9576c5f226a179e6199f2b7a1d837b5c234c5a8e" ] } ``` * `name`: A unique name to identify your verifier application. * `type`: Must be `android` for an Android application. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/verification/android-app-signing) for more information. The response will include a unique `id` for your application, used by the SDK to identify and authenticate your application. ### Supply `PlatformConfiguration` and handle tethering errors at initialization [#supply-platformconfiguration-and-handle-tethering-errors-at-initialization] `PlatformConfiguration` is now required on `initialize`. Previously it was optional and only used for remote mobile (app-to-app) verification flows; it now also drives SDK Tethering, registering the app instance with your MATTR VII tenant and obtaining a license on first initialization. Always pass a `PlatformConfiguration`, and handle the new `InvalidLicenseException` and `FailedToRegisterException` that `initialize` can now throw: ```diff - val platformConfiguration = PlatformConfiguration( - tenantHost = URL("https://your-tenant.vii.mattr.global") - ) - MobileCredentialVerifier.initialize(context, platformConfiguration) + val platformConfiguration = PlatformConfiguration( + tenantHost = URL("https://your-tenant.vii.mattr.global"), + applicationId = "1ef1f867-20b4-48ea-aec1-bea7aff4964c" + ) + try { + MobileCredentialVerifier.initialize(context, platformConfiguration) + } catch (e: FailedToRegisterException) { + // Registration with the MATTR VII tenant failed — check connectivity and configuration + } catch (e: InvalidLicenseException) { + // The SDK license is missing, invalid, or expired + } ``` * `tenantHost`: The URL of your MATTR VII tenant where your verifier application is configured. * `applicationId`: The `id` of your configured MATTR VII verifier application. The majority of the SDK's other APIs can now also throw `InvalidLicenseException` when a valid license is not present. Network access is required the first time the SDK initializes (for registration) and when the license is renewed on subsequent initializations. Update your error handling, logging, analytics, and support diagnostics to account for these cases. ### Remove the `applicationId` argument from `requestMobileCredentials` [#remove-the-applicationid-argument-from-requestmobilecredentials] `MobileCredentialVerifier.requestMobileCredentials` (remote mobile, app-to-app) no longer takes an `applicationId` parameter. It now uses the application ID supplied in `PlatformConfiguration` during initialization. Remove the argument from your call sites: ```diff val result = verifier.requestMobileCredentials( request = listOf(mobileCredentialRequest), challenge = challenge, - applicationId = "your-application-id" ) ``` Ensure you provide `applicationId` via `PlatformConfiguration` (see the previous step) instead. ### Update revocation status list method and type names [#update-revocation-status-list-method-and-type-names] The revocation status list management API has been renamed from `TrustedIssuer`-prefixed terminology to `Revocation` terminology to better reflect its purpose — managing the lists used to check the revocation status of credentials. Update all call sites: ```diff - val result = verifier.updateTrustedIssuerStatusLists() + val result = verifier.refreshRevocationStatusLists() - val cacheInfo = verifier.getTrustedIssuerStatusListsCacheInfo() + val cacheInfo = verifier.getRevocationStatusListsCacheInfo() ``` | Old | New | | ---------------------------------------- | ------------------------------------- | | `updateTrustedIssuerStatusLists()` | `refreshRevocationStatusLists()` | | `getTrustedIssuerStatusListsCacheInfo()` | `getRevocationStatusListsCacheInfo()` | | `TrustedIssuerStatusListsCacheInfo` | `RevocationStatusListsCacheInfo` | | `UpdateTrustedIssuerStatusListsResult` | `RevocationStatusListsRefreshResult` | ### Update revocation status list refresh result handling [#update-revocation-status-list-refresh-result-handling] `RevocationStatusListsRefreshResult` (formerly `UpdateTrustedIssuerStatusListsResult`) has been converted from a data class to a sealed interface with `Success` and `Failure` variants, and the `.success: Boolean` field has been removed. Replace boolean branching with `when`/`is` pattern matching: ```diff - val result = verifier.refreshRevocationStatusLists() - if (result.success) { - // Handle success - } else { - // Handle failure - } + when (val result = verifier.refreshRevocationStatusLists()) { + is RevocationStatusListsRefreshResult.Success -> { + // Handle successful refresh + } + is RevocationStatusListsRefreshResult.Failure -> { + // result.failedLists is available here + } + } ``` ### Update online presentation session result handling [#update-online-presentation-session-result-handling] `OnlinePresentationSessionResult` has been converted from a data class to a sealed interface with `Success` and `Failure` variants. Replace field-based branching with `when`/`is` pattern matching: ```diff - val result = ... // OnlinePresentationSessionResult - if (result.mobileCredentialResponse != null) { - // Use result.mobileCredentialResponse - } else { - // Use result.error - } + when (result) { + is OnlinePresentationSessionResult.Success -> { + // result.mobileCredentialResponse is guaranteed + } + is OnlinePresentationSessionResult.Failure -> { + // result.error is guaranteed + } + } ``` ### Remove optional handling on `MobileCredentialResponse` collections [#remove-optional-handling-on-mobilecredentialresponse-collections] `MobileCredentialResponse.credentials` and `MobileCredentialResponse.credentialErrors` are now required non-nullable `List` fields, instead of nullable fields. Remove null checks and `?.` navigation, and provide both arguments when constructing the type: ```diff - for (credential in response.credentials.orEmpty()) { ... } + for (credential in response.credentials) { ... } ``` # iOS Verifier SDK v5.0.0 Migration Guide URL: /docs/verification/sdks/ios-5.0.0-migration-guide ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in MobileCredentialVerifierSDK v5.0.0 for iOS, including breaking changes, new features, and migration steps. ## Key Features [#key-features] * **Lifecycle management improvements**: New `destroy()` method enables complete SDK reset, plus `deinitialize()` no longer requires `@MainActor` context for improved flexibility. * **Improved reliability in contactless flows**: Enhanced BLE performance delivers more consistent proximity credential exchanges and faster engagements. * **Enhanced storage reliability**: Fixed intermittent storage initialization errors during app launch by migrating key storage from `UserDefaults` to the Keychain with explicit availability checks. * **Better timeout handling**: Proximity presentation session timeouts are now correctly propagated to the caller instead of being silently ignored. For a detailed list of changes included in this release, refer to the [SDK Changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog). ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v5.0.0 that require updates to your existing implementation: | # | Symbol | Change | Impact | | - | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------- | | 1 | `MobileCredentialVerifier.sendProximityPresentationRequest(request:skipStatusCheck:)` | Parameter renamed to `checkStatus:` with **inverted semantics** | All call sites using `skipStatusCheck:` must be updated. | | 2 | `MobileCredentialVerifier.deinitialize()` | No longer `@MainActor` | Can now be called from any actor context. | | 3 | `MobileCredentialVerifierError` | Introduced two new enum cases: `.storageInitializedInBackground`, `.sdkInitialized` | Exhaustive `switch` statements must add new cases. | ## New Additions [#new-additions] ### Methods [#methods] | Method | Purpose | | ----------- | ---------------------------------------------------------------------------------------------------------------------------- | | `destroy()` | Destroys SDK instance and deletes all certificates from storage. Throws `sdkInitialized` if called while SDK is initialized. | ### Enum Cases [#enum-cases] | Type | New Case | Description | | ------------------------------- | --------------------------------- | --------------------------------------------------------------------- | | `MobileCredentialVerifierError` | `.storageInitializedInBackground` | SDK initialized without keychain access (e.g., during app prewarming) | | `MobileCredentialVerifierError` | `.sdkInitialized` | Operation requires SDK to be deinitialized first | ## Deprecations [#deprecations] No new deprecations. ## Bug Fixes [#bug-fixes] * Fixed intermittent "unable to initialize storage" errors during app launch. The issue was caused by using `UserDefaults` for `keyId` storage, which does not guarantee persistence—particularly during iOS app prewarming when protected data may be unavailable. The SDK now stores the `keyId` in the Keychain and performs explicit availability checks before initialization, throwing a new `storageInitializedInBackground` error when the keychain is inaccessible. * Fixed an issue where `ProximityPresentationSession` timeouts were silently ignored instead of throwing an error. The SDK now correctly propagates timeout errors to the caller when the session times out waiting for a response. ## Minimum Requirements [#minimum-requirements] * iOS 15+ for core SDK functionality. ## Migration Steps [#migration-steps] ### Update `sendProximityPresentationRequest` calls [#update-sendproximitypresentationrequest-calls] The `skipStatusCheck` parameter has been renamed to `checkStatus` with inverted semantics. When `checkStatus` is `true` (default), the SDK will verify credential status. When `false`, it will skip status checking. Update all calls accordingly: ```diff - let response = try await verifier.sendProximityPresentationRequest(request: requests, skipStatusCheck: true) + let response = try await verifier.sendProximityPresentationRequest(request: requests, checkStatus: false) ``` | Old Parameter | New Parameter | Mapping | | ---------------------------------- | ----------------------------- | ------------------ | | `skipStatusCheck: false` (default) | `checkStatus: true` (default) | No change needed | | `skipStatusCheck: true` | `checkStatus: false` | Invert the boolean | ### Handle new error cases [#handle-new-error-cases] If you have exhaustive `switch` statements on `MobileCredentialVerifierError`, you must add handlers for the two new error cases: ```diff switch error { case .sdkNotInitialized: // Handle not initialized case .storageInitialization: // Handle storage error + case .storageInitializedInBackground: + // SDK initialized during app prewarming — prompt user to retry + case .sdkInitialized: + // Operation requires deinitialize() first // ... other cases } ``` **Error handling guidance:** * `.storageInitializedInBackground`: This error occurs when the SDK is initialized during iOS app prewarming, before the keychain is accessible. Listen for `UIApplication.protectedDataDidBecomeAvailableNotification` and re-initialize the SDK when keychain access becomes available. * `.sdkInitialized`: This error is thrown by `destroy()` when called while the SDK is still initialized. Call `deinitialize()` first, then retry the operation. ### Use `destroy()` for complete reset (optional) [#use-destroy-for-complete-reset-optional] If you need to completely reset SDK state and delete all stored certificates and credentials, you can now call the new `destroy()` method. This is optional and should be used with caution as it will remove all data: ```swift // Ensure SDK is deinitialized first await MobileCredentialVerifier.shared.deinitialize() // Then destroy all stored data try MobileCredentialVerifier.shared.destroy() // Re-initialize when needed try MobileCredentialVerifier.shared.initialize() ``` ### Handle `deinitialize()` actor context change [#handle-deinitialize-actor-context-change] The `deinitialize()` method is no longer restricted to `@MainActor`. You can now call it from any context without dispatching to the main actor: ```diff - await MainActor.run { - await MobileCredentialVerifier.shared.deinitialize() - } + await MobileCredentialVerifier.shared.deinitialize() ``` # iOS Verifier SDK v6.0.0 Migration Guide URL: /docs/verification/sdks/ios-6.0.0-migration-guide Description: A comprehensive guide to migrating to iOS Verifier SDK v6.0.0, covering breaking changes, new features, and step-by-step migration instructions. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in the iOS Verifier SDK v6.0.0, including breaking changes, new features, and migration steps. This release focuses on strengthening trust between your verifier application and your MATTR VII tenant, improving consistency across platforms, and making verification results more predictable. The headline change is **SDK Tethering**, which becomes **required** in this release: every SDK and app instance is now registered with, and licensed by, your MATTR VII tenant at initialization. Unlike the Holder SDK, where SDK Tethering is optional, **SDK Tethering is required for the Verifier SDK** from v6.0.0. This builds on an existing requirement — remote mobile (app-to-app) verification already required you to supply a `platformConfiguration` so the SDK could reach your MATTR VII tenant to handle the backend verification. That `platformConfiguration` is now mandatory for all initializations and additionally drives SDK Tethering. ## Key Features [#key-features] * **SDK Tethering (required)**: The iOS Verifier SDK is now tethered to a MATTR VII tenant, tying each SDK/app instance to your tenant. On first initialization the SDK registers the app instance with the tenant specified in `PlatformConfiguration` and obtains a license; on subsequent initializations the existing license is renewed automatically. This lets you view registered and active app instances directly from your tenant for operational insight, and establishes a remote management channel we expect to extend in future releases (for example, remote syncing of trusted issuer lists and eventing). The SDK uses [App Attest](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity) during app registration when available on the device. Network access is required when registration or renewal is performed. * **Asynchronous initialization**: `initialize` is now asynchronous, aligning the SDK with modern Swift concurrency and the registration/licensing work performed during tethering. * **Cross-platform alignment**: Verification result types and the revocation status list API have been renamed and restructured to align with the Android Verifier SDK, minimizing divergence for teams maintaining cross-platform applications. * **Simpler challenge handling for remote verification**: The `challenge` parameter for remote mobile (app-to-app) verification is now optional; when omitted, the SDK generates a cryptographically secure challenge for you. * **Clearer connectivity error reporting**: Remote mobile verification now surfaces a dedicated `connectivityError` when the internet connection is lost mid-flow. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency, and improve overall reliability. ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v6.0.0 that require updates to your existing implementation: | # | Change | Impact | | -- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | SDK Tethering is now required: `platformConfiguration` is mandatory on `initialize`, which now also registers the app instance and obtains a license | Always supply a `platformConfiguration`. Handle the new `invalidLicense` and `failedToRegister` errors, which can be thrown by `initialize` and by most SDK APIs. | | 2 | `initialize` is now asynchronous | Add `await` to all call sites and call `initialize` from an asynchronous context. | | 3 | `MobileCredentialVerifierError.platformConfigurationInvalid` removed | Remove handling for this case; `fetchAppleWalletConfiguration(request:merchantId:)` no longer throws it. | | 4 | `VerificationResult` renamed to `MobileCredentialVerificationResult`, with `.reason` renamed to `.failureType` | Update all type references, rename `.reason` to `.failureType`, and remove use of `VerificationFailedReason`. | | 5 | `TrustedCertificateVerificationResult.reason` renamed to `.failureType` | Rename `.reason` to `.failureType` and remove use of `VerificationFailedReason`. | | 6 | `MobileCredentialVerificationFailureType` now serializes as a `{type, message}` object instead of a plain raw-value string | Update any storage or transport layer that persists or forwards these serialized values. | | 7 | `TrustedCertificateVerificationFailureType` now serializes as a `{type, message}` object instead of a plain raw-value string | Update any storage or transport layer that persists or forwards these serialized values. | | 8 | Revocation status list methods and types renamed from `TrustedIssuer`-prefixed terminology to `Revocation` terminology | Rename `updateTrustedIssuerStatusLists` → `refreshRevocationStatusLists`, `getTrustedIssuerStatusListsCacheInfo` → `getRevocationStatusListsCacheInfo`, and the corresponding return types. | | 9 | `RevocationStatusListsRefreshResult` and `OnlinePresentationSessionResult` converted from structs with optional properties to `@frozen` enums with `success` and `failure` cases | Replace property-based branching with `switch`/`case` pattern matching. | | 10 | `applicationId` parameter removed from `fetchAppleWalletConfiguration` and `requestMobileCredentials` | Remove the `applicationId` argument from these call sites; the SDK now uses the `applicationId` from `PlatformConfiguration`. | ## Migration Steps [#migration-steps] ### Create a verifier application on your MATTR VII tenant [#create-a-verifier-application-on-your-mattr-vii-tenant] SDK Tethering requires a verifier application configured on the MATTR VII tenant your SDK connects to. If you already use remote mobile (app-to-app) verification you will have created one; the same application is reused for tethering. If you have not, create one now. To register your iOS application, make a request to create a verifier application: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID" } ``` * `name`: A unique name to identify your verifier application. * `type`: Must be `ios` for an iOS application. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID. The response will include a unique `id` for your application. This is the `applicationId` you supply in `PlatformConfiguration` at initialization, which the SDK now uses for all flows (including remote mobile verification). ### Supply `platformConfiguration` and make `initialize` asynchronous [#supply-platformconfiguration-and-make-initialize-asynchronous] `initialize` is now asynchronous and `platformConfiguration` is required. Previously, `platformConfiguration` was optional and only used for remote mobile (app-to-app) verification flows; it now also drives SDK Tethering, registering the app instance with your MATTR VII tenant and obtaining a license on first initialization. Add `await`, call `initialize` from an asynchronous context, and always pass a `platformConfiguration`: ```diff - let platformConfiguration = PlatformConfiguration( - tenantHost: URL(string: "https://your-tenant.vii.mattr.global")! - ) - try MobileCredentialVerifier.shared.initialize(platformConfiguration: platformConfiguration) + let platformConfiguration = PlatformConfiguration( + tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, + applicationId: "1ef1f867-20b4-48ea-aec1-bea7aff4964c" + ) + try await MobileCredentialVerifier.shared.initialize(platformConfiguration: platformConfiguration) ``` * `tenantHost`: The URL of your MATTR VII tenant where your verifier application is configured. * `applicationId`: The `id` of your configured iOS Verifier Application. Network access is required the first time the SDK initializes (for registration) and when the license is renewed on subsequent initializations. ### Handle license and registration errors [#handle-license-and-registration-errors] Because tethering registers and licenses the SDK, `initialize` can now throw `MobileCredentialVerifierError.invalidLicense` and `MobileCredentialVerifierError.failedToRegister`. The majority of the SDK's other APIs can now also throw `invalidLicense` when a valid license is not present. Update your error handling, logging, analytics, and support diagnostics to account for these cases: ```diff do { try await MobileCredentialVerifier.shared.initialize(platformConfiguration: platformConfiguration) } catch { switch error { + case MobileCredentialVerifierError.failedToRegister: + // Registration with the MATTR VII tenant failed — check connectivity and configuration + case MobileCredentialVerifierError.invalidLicense: + // The SDK license is missing, invalid, or expired // ... other cases } } ``` The `MobileCredentialVerifierError.platformConfigurationInvalid` case has been removed and is no longer thrown by `fetchAppleWalletConfiguration(request:merchantId:)`. Remove any handling for it. ### Update `VerificationResult` to `MobileCredentialVerificationResult` [#update-verificationresult-to-mobilecredentialverificationresult] The `VerificationResult` type has been renamed to `MobileCredentialVerificationResult` and aligned structurally with Android. Its `reason` property has been renamed to `failureType`, typed directly as `MobileCredentialVerificationFailureType?` rather than the now-removed `VerificationFailedReason` wrapper. `MobileCredential.verificationResult` and `MobileCredentialPresentation.verificationResult` now return `MobileCredentialVerificationResult`: ```diff - let result: VerificationResult = credential.verificationResult + let result: MobileCredentialVerificationResult = credential.verificationResult - let failure = result.reason + let failure = result.failureType ``` Replace all references to `VerificationResult` with `MobileCredentialVerificationResult`, rename `.reason` to `.failureType`, and remove any usage of `VerificationFailedReason`. ### Rename `TrustedCertificateVerificationResult.reason` to `failureType` [#rename-trustedcertificateverificationresultreason-to-failuretype] The same `.reason` → `.failureType` rename applies to `TrustedCertificateVerificationResult`. Its `failureType` is now typed directly as `TrustedCertificateVerificationFailureType?` instead of the now-removed `VerificationFailedReason` wrapper: ```diff - let failure = trustedCertificateResult.reason + let failure = trustedCertificateResult.failureType ``` ### Update failure-type serialization handling [#update-failure-type-serialization-handling] `MobileCredentialVerificationFailureType` and `TrustedCertificateVerificationFailureType` now encode and decode as a `{type, message}` object instead of a plain raw-value string: ```diff - "TrustedIssuerCertificateNotFound" + {"type": "TrustedIssuerCertificateNotFound", "message": "Trusted issuer certificate not found"} ``` If you persist or forward the serialized value of either failure type, update your storage or transport layer to produce and consume the new format. ### Update revocation status list method and type names [#update-revocation-status-list-method-and-type-names] The revocation status list management API has been renamed from `TrustedIssuer`-prefixed terminology to `Revocation` terminology to better reflect its purpose — managing the lists used to check the revocation status of credentials. Update all call sites to use the new method names and return types: ```diff - let result = try await verifier.updateTrustedIssuerStatusLists() + let result = try await verifier.refreshRevocationStatusLists() - let cacheInfo = verifier.getTrustedIssuerStatusListsCacheInfo() + let cacheInfo = try verifier.getRevocationStatusListsCacheInfo() ``` | Old | New | | ---------------------------------------- | ------------------------------------- | | `updateTrustedIssuerStatusLists()` | `refreshRevocationStatusLists()` | | `getTrustedIssuerStatusListsCacheInfo()` | `getRevocationStatusListsCacheInfo()` | | `UpdateTrustedIssuerStatusListsResult` | `RevocationStatusListsRefreshResult` | | `TrustedIssuerStatusListsCacheInfo` | `RevocationStatusListsCacheInfo` | ### Update result-type handling for `@frozen` enums [#update-result-type-handling-for-frozen-enums] `RevocationStatusListsRefreshResult` and `OnlinePresentationSessionResult` have been converted from structs with optional properties to `@frozen` enums with `success` and `failure` cases. Replace property-based branching with `switch`/`case` pattern matching. `RevocationStatusListsRefreshResult.success` carries `nextUpdate: Date?`, and `.failure` carries `nextUpdate: Date?` and `failedLists: [String: [String]]`: ```diff - let result = try await verifier.refreshRevocationStatusLists() - if result.success { - // Handle success - } else { - // Handle failure - } + switch try await verifier.refreshRevocationStatusLists() { + case .success(let nextUpdate): + // All status lists refreshed; schedule the next refresh before nextUpdate + case .failure(let nextUpdate, let failedLists): + // failedLists holds the URIs that failed to refresh, keyed by trusted issuer certificate ID + } ``` `OnlinePresentationSessionResult.success` carries `sessionId: String`, `challenge: String?`, and `mobileCredentialResponse: MobileCredentialResponse?`; `.failure` carries `sessionId: String`, `challenge: String?`, and `error: OnlinePresentationResultError`: ```diff - if let response = result.mobileCredentialResponse { - // Use response - } else { - // Use result.error - } + switch result { + case .success(_, _, let mobileCredentialResponse): + // mobileCredentialResponse?.credentials holds the presented credentials + case .failure(_, _, let error): + // error.type and error.message describe the failure + } ``` ### Remove the `applicationId` argument from `fetchAppleWalletConfiguration` and `requestMobileCredentials` [#remove-the-applicationid-argument-from-fetchapplewalletconfiguration-and-requestmobilecredentials] The `applicationId` parameter has been removed from `fetchAppleWalletConfiguration` and `requestMobileCredentials`. The SDK now uses the `applicationId` supplied in `PlatformConfiguration` during initialization. Remove the argument from your call sites: ```diff let result = try await verifier.requestMobileCredentials( request: [mobileCredentialRequest], challenge: challenge, - applicationId: "your-application-id" ) ``` Ensure you provide `applicationId` via `PlatformConfiguration` (see the earlier step) instead. ### (Optional) Simplify challenge handling for remote mobile verification [#optional-simplify-challenge-handling-for-remote-mobile-verification] The `challenge` parameter in `requestMobileCredentials` for remote mobile (app-to-app) verification is now optional. When omitted or left blank, the SDK generates a cryptographically secure random 32-byte challenge automatically, so you no longer need to manage challenge generation yourself: ```diff - let result = try await verifier.requestMobileCredentials( - request: [mobileCredentialRequest], - challenge: UUID().uuidString - ) + let result = try await verifier.requestMobileCredentials( + request: [mobileCredentialRequest] + ) ``` This is an optional improvement; supplying your own `challenge` continues to work. ### (Optional) Handle connectivity errors in remote mobile verification [#optional-handle-connectivity-errors-in-remote-mobile-verification] `requestMobileCredentials` for remote mobile (app-to-app) verification now throws `MobileCredentialVerifierError.connectivityError` if the internet connection is lost during the flow. Handle this case to provide clear feedback and retry guidance to your users: ```diff do { let result = try await verifier.requestMobileCredentials(request: requests) } catch { + if case MobileCredentialVerifierError.connectivityError = error { + // Prompt the user to check their connection and retry + } } ``` # Verifier Mobile SDKs Overview URL: /docs/verification/sdks/overview ## Overview [#overview] The mDocs Verifier SDK are based on the [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) and [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) standards, which establish an interoperable digital representation of mobile-based credentials such as mobile drivers licenses (mDL). However, these SDKs can extend the same technology and architecture to more than just mDLs, but rather any conforming mobile document ([mDoc](/docs/concepts/mdocs)) - a term defined in ISO/IEC 18013-5. The mDocs Verifier SDKs are available for React Native, iOS, and Android. They help developers add mDocs verification capabilities to their apps, allowing secure and privacy preserving verification of presented mDocs in various interoperable workflows. To get started with any of our mDocs Verifier SDKs, please [contact us](mailto:sales@mattr.global). ## SDK Capabilities [#sdk-capabilities] The mDocs Verifier SDKs offer tools to assist developers integrating the following capabilities into their applications: * Interface with an mDoc holder to request presentations of issued mDocs via: * Proximity verification: Verify an mDoc presented in-person as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Remote mobile app verification: Remotely verify an mDoc presented from a different app installed on the same mobile device (as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html)). * Manage a list of trusted issuer certificates which presented mDocs can be validated against. * Manage status lists which can be used to check mDocs' [revocation status](/docs/issuance/revocation/overview). * Interface with an mDoc holder to request presentations of issued mDocs via: * Proximity verification: Verify an mDoc presented in-person as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Remote mobile app verification: Remotely verify an mDoc presented from a different app installed on the same mobile device (as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html)). * Manage a list of trusted issuer certificates which presented mDocs can be validated against. * Manage status lists which can be used to check mDocs' [revocation status](/docs/issuance/revocation/overview). * Interface with an mDoc holder to request presentations of issued mDocs via: * Proximity verification: Verify an mDoc presented in-person as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Manage a list of trusted issuer certificates which presented mDocs can be validated against. * Manage status lists which can be used to check mDocs' [revocation status](/docs/issuance/revocation/overview). ## Supported features [#supported-features] ### Supported ISO/IEC 18013-5 Features [#supported-isoiec-18013-5-features] Below is a summary of ISO/IEC 18013-5 features supported by the mDocs Verifier SDKs: | Feature | Supported options | Default | | :------------------------------ | :----------------------------------------------------------------- | :------------------------------- | | Device engagement | QR code | QR code | | Device retrieval data transport | BLE with either `mDocPeripheralServer` or `mDocCentralClient` mode | Determined by holder application | | Ephemeral session key curve | Any NIST P-\* keys | Determined by holder application | | IACA public key curves | P-256, P-384, P-521 | Determined by Issuer | | Device authentication mode | Digital Signature or ECDH-agreed MAC | Determined by holder application | | Feature | Supported options | Default | | :------------------------------ | :----------------------------------------------------------------- | :------------------------------- | | Device engagement | QR code and NFC | QR code | | Device retrieval data transport | BLE with either `mDocPeripheralServer` or `mDocCentralClient` mode | Determined by holder application | | Ephemeral session key curve | Any NIST P-\* keys | Determined by holder application | | IACA public key curves | P-256, P-384, P-521 | Determined by Issuer | | Device authentication mode | Digital Signature or ECDH-agreed MAC | Determined by holder application | | Feature | Supported options | Default | | :------------------------------ | :----------------------------------------------------------------- | :------------------------------- | | Device engagement | QR code | QR code | | Device retrieval data transport | BLE with either `mDocPeripheralServer` or `mDocCentralClient` mode | Determined by holder application | | Ephemeral session key curve | Any NIST P-\* keys | Determined by holder application | | IACA public key curves | P-256, P-384, P-521 | Determined by Issuer | | Device authentication mode | Digital Signature or ECDH-agreed MAC | Determined by holder application | ## System requirements [#system-requirements] The SDK is developed in the [Swift](https://developer.apple.com/swift/) programming language and is meant for integration into iOS applications developed in Swift and/or Objective-C. Specifically, it currently only supports applications developed in iOS 15 and above. This SDK is developed in the Kotlin programming language and is meant for integration into Android applications. It currently supports Android 7 (API level 24) and above. This SDK is meant for integration into React Native applications using React Native 0.78 and above. Supported operating systems are: * iOS 15 or higher. * Android 7 or higher. ## Dependencies [#dependencies] This section lists all dependencies for using mDocs Verifier SDKs. **Third party dependencies** * [CBORCoding](https://github.com/SomeRandomiOSDev/CBORCoding) (MIT license). * [swift-certificates](https://github.com/apple/swift-certificates.git) 1.7.0 (Apache-2.0 License). * [swift-asn1](https://github.com/apple/swift-asn1) 1.3.1 (Apache-2.0 License). **Apple frameworks** * [Security](https://developer.apple.com/documentation/security) * [CryptoKit](https://developer.apple.com/documentation/cryptokit/) * [LocalAuthentication](https://developer.apple.com/documentation/localauthentication) * [CoreBluetooth](https://developer.apple.com/documentation/corebluetooth) * [Combine](https://developer.apple.com/documentation/combine) * [OSLog](https://developer.apple.com/documentation/oslog) * [UIKit](https://developer.apple.com/documentation/uikit) * [AppKit (on macOS)](https://developer.apple.com/documentation/appkit) * [PassKit](https://developer.apple.com/documentation/passkit) * [SwiftUI](https://developer.apple.com/documentation/swiftui) **Toolchain dependencies** * Swift 5.10. * iOS support: The SDK functionality is only available for devices from iOS 15 onwards. * Xcode: The SDK is built with the latest stable Xcode available in GitHub Actions (setup-xcode action with `latest-stable` label). The current version is Xcode 26.0.0 (17A324). * macOS 15. **Kotlin, AGP, Gradle, and Android Studio** The Android Verifier SDK is built using Kotlin 2.0. This adds some intrinsic dependencies into your build tools. * Kotlin 2.0 is supported from AGP version [8.5](https://developer.android.com/build/kotlin-support). * AGP 8.5 is supported from Gradle version [8.7](https://developer.android.com/build/releases/about-agp) and Android Studio Koala [2024.1.1](https://developer.android.com/studio/releases). **Runtime** A list of runtime dependencies and licenses is generated at build time and packaged in `res/raw/dependencies_licenses.html`. The majority are Kotlin and Android, the rest are listed below: * [CBOR-Java](https://github.com/peteroupc/CBOR-Java) - [Public Domain](https://github.com/peteroupc/CBOR-Java/blob/master/LICENSE.md) * [Timber](https://github.com/JakeWharton/timber) - [Apache 2.0](https://github.com/JakeWharton/timber/blob/trunk/LICENSE.txt) **Standard libraries** * [androidx.activity:activity-ktx:1.9.0](https://developer.android.com/jetpack/androidx/releases/activity#1.9.0) * [androidx.annotation:annotation:1.8.1](https://developer.android.com/jetpack/androidx/releases/annotation#1.8.1) * [androidx.appcompat:appcompat:1.7.0](https://developer.android.com/jetpack/androidx/releases/appcompat#1.7.0) * [androidx.biometric:biometric-ktx:1.2.0-alpha05](https://developer.android.com/jetpack/androidx/releases/biometric#1.2.0-alpha05) * [androidx.browser:browser:1.8.0](https://developer.android.com/jetpack/androidx/releases/browser#1.8.0) * [androidx.core:core-ktx:1.15.0](https://developer.android.com/jetpack/androidx/releases/core#1.15.0) * [androidx.credentials:credentials-play-services-auth:1.5.0](https://developer.android.com/jetpack/androidx/releases/credentials#1.5.0) * [androidx.credentials:credentials:1.5.0](https://developer.android.com/jetpack/androidx/releases/credentials#1.5.0) * [androidx.fragment:fragment:1.5.7](https://developer.android.com/jetpack/androidx/releases/fragment#1.5.7) * [org.jetbrains.kotlin:kotlin-reflect:1.9.22](https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect/1.9.22) * [org.jetbrains.kotlin:kotlin-stdlib:2.0.0](https://kotlinlang.org/) * [org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.3) * [org.jetbrains.kotlinx:kotlinx-datetime:0.4.0](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-datetime-jvm/0.4.0) * [org.jetbrains.kotlinx:kotlinx-io-bytestring:0.6.0](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-io-bytestring-tvosarm64/0.6.0) * [org.jetbrains.kotlinx:kotlinx-io-core:0.6.0](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-io-core/0.6.0) * [org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-serialization-json/1.6.3) **Third-party libraries** * [com.jakewharton.timber:timber:5.0.1](https://mvnrepository.com/artifact/com.jakewharton.timber/timber/5.0.1) * [com.upokecenter:cbor:4.5.2](https://mvnrepository.com/artifact/com.upokecenter/cbor/4.5.2) * None. ## Versions [#versions] Below are the available versions of the mDocs Verifier Mobile SDKs, including the current active version, supported versions, and those that have reached end-of-life (EOL). | Major version | Status | Latest release | End of Life date | Documentation | | ------------- | ----------- | -------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | v6 | Active | 6.0.0 | - | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/6.0.0/documentation/mobilecredentialverifiersdk/) | | v5 | Maintenance | 5.1.1 | 26-09-2026 | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/5.1.1/documentation/mobilecredentialverifiersdk/) | | v4 | End of Life | 4.1.0 | 13-05-2026 | - | | v3 | End of Life | 3.0.0 | 7-10-2025 | - | | v2 | End of Life | 2.0.0 | 26-08-2025 | - | | v1 | End of Life | 1.0.1 | 05-05-2025 | - | | Major version | Status | Latest release | End of Life date | Documentation | | ------------- | ----------- | -------------- | ---------------- | -------------------------------------------------------------------------------------------- | | v7 | Active | 7.0.0 | - | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/7.0.0/) | | v6 | Maintenance | 6.1.1 | 26-09-2026 | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/6.1.1/) | | v5 | End of Life | 5.3.2 | 13-05-2026 | - | | v4 | End of Life | 4.1.1 | 30-12-2025 | - | | v3 | End of Life | 3.0.0 | 7-10-2025 | - | | v2 | End of Life | 2.0.0 | 26-08-2025 | - | | v1 | End of Life | 1.0.1 | 05-05-2025 | - | | Major version | Status | Latest release | End of Life date | Documentation | | ------------- | ----------- | -------------- | ---------------- | ----------------------------------------------------------------------------------------------------------- | | v9 | Active | 9.0.3 | - | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/9.0.3/index.html) | | v8 | End of Life | 8.1.1 | 23-06-2026 | - | | v7 | End of Life | 7.1.0 | 12-12-2025 | - | | v6 | End of Life | 6.0.0 | 26-08-2025 | - | | v5 | End of Life | 5.0.0 | 05-05-2025 | - | | v4 | End of Life | 4.1.1 | 13-04-2025 | - | | v3 | End of Life | 3.0.0 | 15-02-2025 | - | | v2 | End of Life | 2.0.0 | 17-12-2024 | - | | v1 | End of Life | 1.0.1 | 05-07-2024 | - | Release candidates (RC) are pre-release versions that may contain new features or changes that are not yet fully tested. They are intended for testing purposes, are not subject to our SLA and should not be used in production environments. # React Native Verifier SDK v9.0.0 Migration Guide URL: /docs/verification/sdks/react-native-9.0.0-migration-guide Description: A guide to help developers migrate from React Native Verifier SDK v8.x to v9.0.0, including breaking changes, new features, and best practices. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in React Native Verifier SDK v9.0.0, including breaking changes, new features, and migration steps. ## Key Features [#key-features] * **App to app verification**: The React Native Verifier SDK can now be used to request credentials from another app on the same device using OID4VP. This allows you to build verification flows directly into your apps and have a holder app on the same device respond. * **Verify with Apple Wallet (iOS Only)**: The SDK can now request and verify a credential directly from an Apple Wallet using the Verify with Wallet API. Only available for iOS 16 and above. * **Improved reliability in contactless flows**: Enhanced BLE performance delivers more consistent proximity credential exchanges and faster engagements. * **Status Lists Draft 14 Support**: The SDK now supports the Token Status List Draft 14 specification while maintaining existing support for Draft 3. * **Stronger cryptography and standards alignment**: Updated COSE algorithms (as per RFC 9864) strengthen cryptographic compatibility and ensure continued compliance with evolving standards. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency across mobile environments, and improve overall user experience. For a detailed list of changes included in this release, refer to the [SDK Changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log). ## Breaking Changes [#breaking-changes] | # | Element | Change | Impact | | - | ------------------------------------------------------ | ------------------------------------------------------------------------------- | ---------------------------------------------------------- | | 1 | `initialize()` | Now accepts options and returns a `Result` type. | All call sites must handle the result and possible errors. | | 2 | `sendProximityPresentationRequest` | Option renamed from `skipStatusCheck` to `checkStatus` with inverted semantics. | All call sites using `skipStatusCheck` must be updated. | | 3 | `ProximityPresentationSessionTerminationErrorType` | New `Exception` value added. | Exhaustive switches must handle new case. | | 4 | NFC error listener in `registerForNfcDeviceEngagement` | Now routes parse failures to `onError` instead of rethrowing. | Update error handling logic accordingly. | ## New Additions [#new-additions] ### Functions [#functions] | Function | Description | Platform | | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | `fetchAppleWalletConfiguration(options)` | Fetches Apple Wallet configuration needed before initiating the Apple Wallet verification flow. Returns an `AppleWallet` object. | iOS only | | `handleDeepLink(options)` | Handles a deep link URL to continue the online presentation flow. | iOS only | | `requestMobileCredentials(options)` | Remote app-to-app credential verification via Digital Credential Manager / OID4VP. Returns `OnlinePresentationSessionResult`. | Android (DCM/OID4VP), iOS (OID4VP only) | | `destroy()` | Destroys the verifier SDK instance. Returns an error if called while the SDK is initialized; deinitialize the SDK before calling. | All | | `getCurrentLogFilePath()` | Returns path to the Verifier SDK log file. | All | ### Updated Function Signatures [#updated-function-signatures] * `initialize(options?)` — new `InitializeOptions` parameter: * `loggerConfiguration?: LoggerConfiguration` — `logLevel`, `callbackLogLevel`, optional `logDir`, optional `callback` * `platformConfiguration?: PlatformConfiguration` — `tenantHost: string` (required for `requestMobileCredentials`) ### New initialize Options [#new-initialize-options] * `loggerConfiguration?: LoggerConfiguration` — configure SDK logging: logLevel, callbackLogLevel, logDir, callback on log events. * `platformConfiguration?: PlatformConfiguration` — set MATTR VII tenant host for remote credential requests. ### New Types & Enums [#new-types--enums] * **Online presentation (remote/app-to-app):** * `OnlinePresentationSessionResult` — `{ sessionId, challenge?, mobileCredentialResponse?, error? }` * `OnlinePresentationResultError` — `{ type: OnlinePresentationResultErrorType, message }` * `OnlinePresentationResultErrorType` — `SessionAborted | VerificationError | ResponseError | WalletUnavailable | Unknown` * **Apple Wallet:** * `AppleWallet` — object with `requestMobileCredentials(challenge): Promise>` * `RequestMobileCredentialsWithAppleWalletError` / `RequestMobileCredentialsWithAppleWalletErrorType` * **`requestMobileCredentials` options & errors:** * `RequestMobileCredentialsOptions` — `{ request, applicationId, walletProviderId?, challenge }` * `RequestMobileCredentialsErrorType` * `RequestMobileCredentialsError` * **`handleDeepLink`:** * `HandleDeepLinkOptions` — `{ url: string }` * **`fetchAppleWalletConfiguration`:** * `FetchAppleWalletConfigurationOptions` — `{ request, applicationId, merchantId }` * `FetchAppleWalletConfigurationError` / `FetchAppleWalletConfigurationErrorType` * **`initialize`:** * `InitializeOptions`, `LoggerConfiguration`, `PlatformConfiguration`, `LogLevel` (`Off | Error | Warn | Info | Debug | Verbose`) * `InitializeErrorType` = `SdkInitialized | StorageInitializedInBackground` ### New Error Values [#new-error-values] | Location | New values | | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MobileCredentialVerifierErrorType` | `Connectivity`, `StorageInitializedInBackground`, `SdkInitialized`, `PlatformNotSupported`, `PlatformConfigurationInvalid`, `SessionTimedOut`, `SessionAborted`, `DigitalCredentialManager`, `UserCanceled`, `FailedToRequestMobileCredentials`, `AppleWalletNotAvailable` | | `ProximityPresentationSessionTerminationErrorType` | `Exception` | ## Minimum Requirements [#minimum-requirements] **iOS** * iOS 15+ for core SDK functionality. **Android** * Android 7 / Nougat / API 24. * The underlying Android Verifier SDK is built using Kotlin 2.0. This adds some intrinsic dependencies into your build tools. * Kotlin 2.0 is supported from AGP version [8.5](https://developer.android.com/build/kotlin-support). * AGP 8.5 is supported from Gradle version [8.7](https://developer.android.com/build/releases/about-agp) and Android Studio Koala [2024.1.1](https://developer.android.com/studio/releases). ## Migration Steps [#migration-steps] ### Update `initialize()` usage [#update-initialize-usage] The `initialize()` function now accepts an optional options object and returns a `Result`. Update your code to handle the result and possible errors: ```diff - await initialize(); + const result = await initialize(options); + if (result.isErr()) { + // Handle error: result.error + } ``` ### Update `sendProximityPresentationRequest` calls [#update-sendproximitypresentationrequest-calls] The `skipStatusCheck` parameter has been renamed to `checkStatus` with inverted semantics. When `checkStatus` is `true` (default), the SDK will verify credential status. When `false`, it will skip status checking. Update all calls accordingly: ```diff - const response = await sendProximityPresentationRequest({ ..., skipStatusCheck: true }); + const response = await sendProximityPresentationRequest({ ..., checkStatus: false }); ``` | Old Parameter | New Parameter | Mapping | | ----------------------------------- | ------------------------------ | ------------------ | | `skipStatusCheck = false` (default) | `checkStatus = true` (default) | No change needed | | `skipStatusCheck = true` | `checkStatus = false` | Invert the boolean | ### Handle new `ProximityPresentationSessionTerminationErrorType.Exception` case [#handle-new-proximitypresentationsessionterminationerrortypeexception-case] If you switch over `ProximityPresentationSessionTerminationErrorType`, add handling for the new `Exception` value. ### Update NFC error handling [#update-nfc-error-handling] The NFC error listener in `registerForNfcDeviceEngagement` now routes parse failures to `onError` instead of rethrowing. Update your error handling logic accordingly. # Getting started with the Verifier SDKs URL: /docs/verification/sdks/sdk-getting-started Description: Set up access to the MATTR Pi mDocs Verifier SDKs, configure SDK tethering, and initialize the SDK in your mobile application. This guide walks you through the steps required to start building with the MATTR Pi mDocs Verifier SDKs. By the end, your mobile application will be ready to verify credential presentations. For the native iOS and Android Verifier SDKs, this includes tethering your application to a MATTR VII tenant. The React Native Verifier SDK is not tethered: for in-person (proximity) verification it does not require a MATTR VII tenant or platform configuration, and only needs a tenant for remote mobile (app-to-app) verification. React Native differences are called out at each step below. ### Request SDK access [#request-sdk-access] To access the MATTR Pi mDocs Verifier SDKs, complete the [Get Started form](/docs/resources/get-started) with the following details: * Your organization name and contact information. * The platform(s) you plan to build for (iOS, Android, or React Native). * A brief description of your use case. ### Create a MATTR VII tenant [#create-a-mattr-vii-tenant] The native iOS and Android Verifier SDKs require a MATTR VII tenant that serves as the backend for SDK operations including tethering and credential verification. For React Native, a tenant is only required for remote mobile (app-to-app) verification. If you are building React Native for in-person verification only, you can skip this step. 1. Log into the [MATTR Portal](https://portal.mattr.global). 2. Select the **Create/switch tenant** button on the top-right side of the screen.\ The *All tenants* panel is displayed, listing any existing tenants. 3. Select the **Create new** button.\ The *New tenant* form is displayed. 4. Use the *Region* dropdown list to select the region your tenant will be hosted in. 5. Use the *Tenant subdomain* text box to insert a subdomain for your tenant (e.g. `in-person-verification`). 6. Select the **Create** button to create the new tenant. 7. Copy the displayed tenant information (`audience`, `auth_url`, `tenant_url`, `client_id` and `client_secret`). ### Create a Verifier Application [#create-a-verifier-application] The iOS and Android Verifier SDKs are **tethered** to a MATTR VII tenant. On initialization, the SDK registers your app instance with the tenant and obtains a license, so SDK Tethering must be configured before you initialize the SDK. For a full explanation of tethering and the capabilities it enables, see [SDK Tethering](/docs/verification/sdks/sdk-tethering). To tether the SDK, create a Verifier Application on your MATTR VII tenant for the platform you are building. Make a request of the following structure to create an iOS Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `ios`. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID (must match the Team ID used to sign your app). * `appAttest`: App Attest configuration for the iOS verifier application: * `required`: When `true`, the app instance must provide a valid App Attest attestation during registration and token renewal. When `false`, the app can fall back to assertion-only authentication. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `environment`: The App Attest environment (`development` or `production`). Apple recommends using `development` for testing and `production` for distribution builds. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", // [!code focus] "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. Make a request of the following structure to create an Android Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false }, "openid4vpConfiguration": { "redirectUri": "com.yourcompany.verifierapp://oid4vp-callback" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `android`. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/verification/android-app-signing) for more information. * `keyAttestation`: Key Attestation configuration for the Android verifier application: * `required`: When `true`, the app instance must provide a valid Key Attestation during registration and token renewal. When `false`, the app can register and renew tokens using just an authentication assertion. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `openid4vpConfiguration.redirectUri`: Required by the create-application endpoint, which needs at least one of `openid4vpConfiguration` or `dcApiConfiguration`. In-person proximity verification does not use this redirect, so any valid custom-scheme URI is accepted here. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", // [!code focus] "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. SDK Tethering is currently not required for the React Native Verifier SDK. ### Initialize the SDK [#initialize-the-sdk] When you initialize the SDK, you must provide a `PlatformConfiguration` object with your tenant host and the `id` of the Verifier Application you created. This allows the SDK to register the app instance with your tenant and obtain a license to operate. Initialize the SDK with your platform configuration. The `initialize` method is asynchronous, so call it from an asynchronous context: ```swift title="Initialization" let platformConfig = PlatformConfiguration( tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, applicationId: "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) try await MobileCredentialVerifier.shared.initialize( platformConfiguration: platformConfig ) ``` * `tenantHost`: The URL of your MATTR VII tenant. This must be the tenant where your iOS Verifier Application is configured. * `applicationId`: The `id` of your configured iOS Verifier Application. Initialize the SDK with your platform configuration: ```kotlin title="Initialization" val platformConfig = PlatformConfiguration( tenantHost = URL("https://your-tenant.vii.mattr.global"), applicationId = "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) MobileCredentialVerifier.initialize(context, platformConfig) ``` * `tenantHost`: The URL of your MATTR VII tenant where your Android Verifier Application is configured. * `applicationId`: The `id` of your configured Android Verifier Application. The React Native Verifier SDK is **not** tethered, so initialization does not register an app instance or require a `platformConfiguration` object. ## Next steps [#next-steps] Your application is now initialized and tethered to your MATTR VII tenant, ready to verify credentials. Explore the following resources to start building: * In-person verification: * [Quickstart](/docs/verification/in-person-quickstart): Run a sample in-person verifier app end-to-end. * [Tutorial](/docs/verification/in-person-tutorial): Detailed walkthrough of building an app that can verify credentials in-person using Bluetooth proximity presentations. * Remote mobile verification: * [Quickstart](/docs/verification/remote-mobile-verifiers/quickstart): Run a sample remote mobile verifier app end-to-end. * [Tutorial](/docs/verification/remote-mobile-verifiers/tutorial): Detailed walkthrough of building an app that can request and verify credentials from a wallet app on the same device (app-to-app). # Configure SDK logging URL: /docs/verification/sdks/sdk-logging Description: Learn how to configure logging in the MATTR Verifier SDKs, including log levels, callback handlers, and accessing log files across iOS, Android, and React Native platforms. The MATTR Verifier SDKs include a built-in logging system that records internal SDK operations. This is useful for debugging integration issues, monitoring SDK behavior, and capturing diagnostic information during development and testing. By default, SDK logs are stored on the device. The SDK itself does not transmit logs to any external service, although your application can choose to forward log events elsewhere if you register a callback. ## What information the SDK can log [#what-information-the-sdk-can-log] The SDK can log information about its internal operations, including errors and warnings encountered during SDK operations. All log entries include the log level and a descriptive message. This information helps you diagnose issues and understand how the SDK operates within your application. ## Log levels [#log-levels] The SDK supports the following log levels, ordered from most to least verbose: | Level | Description | | --------- | -------------------------------------------------------------------------- | | `Verbose` | Fine-grained informational events, most detailed output. | | `Debug` | Detailed information useful during development. | | `Info` | General informational messages about SDK operations. | | `Warning` | Potentially harmful situations or unexpected behavior (`Warn` in Android). | | `Error` | Error events that might still allow the SDK to continue running. | | `Assert` | Severe error events that indicate a critical failure (Android only). | | `Off` | Disables logging entirely. | The SDK uses the configured log level as a threshold: it records log events at the specified level and any less verbose levels. For example, setting the level to `Info` captures `Info`, `Warning`, `Error`, and `Assert` events, but not `Debug` or `Verbose`. ## Configure logging at initialization [#configure-logging-at-initialization] You can configure logging behavior by passing a `loggerConfiguration` object to the SDK's `initialize` method. This configuration accepts two separate log levels: * **`logLevel`**: Controls which log events are written to the log file. * **`callbackLogLevel`**: Controls which log events trigger the optional callback function. ```swift title="Configure logging during initialization" try mobileCredentialVerifier.initialize( loggerConfiguration: LoggerConfiguration( // [!code highlight] logLevel: .Info, // [!code highlight] callbackLogLevel: .Warning // [!code highlight] ) // [!code highlight] ) ``` * `logLevel`: Sets the minimum level for writing log entries to the log file. Defaults to `.Off`. * `callbackLogLevel`: Sets the minimum level for invoking the callback closure. Defaults to `.Off`. Refer to the [`LoggerConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/loggerconfiguration) reference documentation for additional details. ```kotlin title="Configure logging during initialization" mobileCredentialVerifier.initialize( loggerConfiguration = Logger.LoggerConfiguration( logLevel = Logger.LogLevel.INFO, callbackLogLevel = Logger.LogLevel.WARN ), // ... ) ``` * `logLevel`: Sets the minimum level for writing log entries to the log file and Logcat. Defaults to `Logger.LogLevel.OFF`. * `callbackLogLevel`: Sets the minimum level for invoking the callback function. Defaults to `Logger.LogLevel.OFF`. * `logDir`: Optional. Specifies a local directory to store log files. If not provided, logs won't be stored to file. Refer to the [`Logger.LoggerConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier.util/-logger/-logger-configuration/index.html) reference documentation for additional details. ```ts title="Configure logging during initialization" import { LogLevel } from "@mattrglobal/mobile-credential-verifier-react-native" const initializeResult = await mobileCredentialVerifier.initialize({ loggerConfiguration: { // [!code highlight] logLevel: LogLevel.Info, // [!code highlight] callbackLogLevel: LogLevel.Warn, // [!code highlight] }, // [!code highlight] }) if (initializeResult.isErr()) { const { error } = initializeResult // handle error scenarios return } ``` * `logLevel`: Sets the minimum level for writing log entries to the log file and console. Defaults to `LogLevel.Off`. * `callbackLogLevel`: Sets the minimum level for invoking the callback function. Defaults to `LogLevel.Off`. * `logDir`: Optional. Specifies a directory to store log files. On iOS, a default directory is used. On Android, logs won't be stored to file if not provided. Refer to the [`LoggerConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/types/LoggerConfiguration.html) reference documentation for additional details. ## Handle log events with a callback [#handle-log-events-with-a-callback] You can register a callback function during initialization to receive log events in real time. This allows your application to process log events as they occur, for example to forward them to a custom logging service, display them in a debug console, or filter specific events for monitoring. The callback is only invoked for log events at or above the `callbackLogLevel` threshold. ```swift title="Register a logging callback" try mobileCredentialVerifier.initialize( loggerConfiguration: LoggerConfiguration( logLevel: .Info, callbackLogLevel: .Warning, callback: { logEvent in // [!code highlight] print("[\(logEvent.level)] \(logEvent.message)") // [!code highlight] } // [!code highlight] ) ) ``` The callback receives a [`LogEvent`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/logevent) object with the following properties: * `level`: The [`LogLevel`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/loglevel) of the event. * `message`: A string describing the log event. ```kotlin title="Register a logging callback" mobileCredentialVerifier.initialize( loggerConfiguration = Logger.LoggerConfiguration( logLevel = Logger.LogLevel.INFO, callbackLogLevel = Logger.LogLevel.WARN, callback = { priority, tag, message, throwable -> // [!code highlight] Log.d("VerifierSDK", "[$tag] $message") // [!code highlight] } // [!code highlight] ), // ... ) ``` The callback function receives the following parameters: * `priority`: An integer representing the log priority level. * `tag`: An optional string tag identifying the log source. * `message`: A string describing the log event. * `throwable`: An optional `Throwable` associated with the log event (for error-level logs). ```ts title="Register a logging callback" import { LogLevel } from "@mattrglobal/mobile-credential-verifier-react-native" const initializeResult = await mobileCredentialVerifier.initialize({ loggerConfiguration: { logLevel: LogLevel.Info, callbackLogLevel: LogLevel.Warn, callback: (log) => { // [!code highlight] console.log(`[${log.logLevel}] ${log.tag ?? ""}: ${log.message ?? ""}`) // [!code highlight] }, // [!code highlight] }, }) if (initializeResult.isErr()) { const { error } = initializeResult // handle error scenarios return } ``` The callback receives a log object with the following properties: * `logLevel`: The [`LogLevel`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/enums/LogLevel.html) of the event. * `message`: An optional string describing the log event. * `tag`: An optional string tag identifying the log source. ## Access the log file [#access-the-log-file] The SDK writes log entries to a file that you can access for debugging and diagnostics. The log file contains entries from the previous two calendar days. ```swift title="Get the log file path" let logFilePath = mobileCredentialVerifier.getCurrentLogFilePath() ``` The [`getCurrentLogFilePath`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialverifier/getcurrentlogfilepath\(\)) method returns the file path as a string, or `nil` if no log file is available. To read the logs, use the returned file path to load the file contents into `Data`, then decode the data into text and split it into individual log messages as needed. ```kotlin title="Get the log file path" val logFilePath = mobileCredentialVerifier.getLog() ``` The [`getLog`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-mobile-credential-verifier/get-log.html) method returns the full path of the log file (with a `.log` extension), or `null` if no log file is available. The file contains log entries from the previous two calendar days. ```ts title="Get the log file path" const logFilePath = await mobileCredentialVerifier.getCurrentLogFilePath() ``` The [`getCurrentLogFilePath`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/functions/getCurrentLogFilePath.html) method returns a `Promise` resolving to the log file path string, or `null` if no log file is available. # SDK Tethering URL: /docs/verification/sdks/sdk-tethering Description: Learn how MATTR Verifier SDKs are tethered to a MATTR VII tenant through Verifier Application configuration, enabling operational insights and licensing. SDK Tethering ties each SDK/app instance to a MATTR VII tenant. Tethering establishes a trust relationship between your mobile application and the MATTR VII tenant, enabling the following capabilities: * **Operational insights**: View details about registered and active app instances directly from your tenant. * **Licensing**: On first initialization, the SDK registers the app instance with your tenant and obtains a license. The majority of the SDK's APIs require a valid license to operate. * **Remote management channel**: SDK Tethering establishes a channel that we expect to extend in the future with capabilities such as remote syncing of trusted issuer lists and eventing. SDK Tethering is required from the following SDK versions: * **iOS Verifier SDK**: 6.0.0 * **Android Verifier SDK**: 7.0.0 ## How it works [#how-it-works] The tethering process involves three steps: 1. **Configure a Verifier Application on your MATTR VII tenant**: You register your mobile app by creating a Verifier Application, identified by the bundle identifier and team ID (iOS) or the package fingerprint (Android). 2. **Initialize the SDK with your tenant details**: When you initialize the SDK in your app, you pass the details of the MATTR VII tenant and the Verifier Application you configured on it. 3. **Automatic communication**: Once initialized, instances of your app will automatically communicate with the configured MATTR VII tenant and retrieve the required tokens to operate and make requests to the tenant when required. ## Token validity and offline use [#token-validity-and-offline-use] The tokens issued during this process have configurable validity periods controlled by the `maxTimeOfflineInSecs` field on your Verifier Application configuration. This means your app can function without internet connectivity to meet different use cases: * **Minimum**: 1 day (86400 seconds) * **Maximum**: 30 days (2592000 seconds) * **Default**: 7 days (604800 seconds) When the license token expires, the SDK must reconnect to the MATTR VII tenant to renew it. Network access is required when registration or renewal is performed. ## Configuring SDK Tethering [#configuring-sdk-tethering] ### Configure Verifier Applications [#configure-verifier-applications] Make a request of the following structure to create an iOS Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `ios`. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID (must match the Team ID used to sign your app). * `appAttest`: App Attest configuration for the iOS verifier application: * `required`: When `true`, the app instance must provide a valid App Attest attestation during registration and token renewal. When `false`, the app can fall back to assertion-only authentication. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `environment`: The App Attest environment (`development` or `production`). Apple recommends using `development` for testing and `production` for distribution builds. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", // [!code focus] "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. Make a request of the following structure to create an Android Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false }, "openid4vpConfiguration": { "redirectUri": "com.yourcompany.verifierapp://oid4vp-callback" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `android`. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/verification/android-app-signing) for more information. * `keyAttestation`: Key Attestation configuration for the Android verifier application: * `required`: When `true`, the app instance must provide a valid Key Attestation during registration and token renewal. When `false`, the app can register and renew tokens using just an authentication assertion. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `openid4vpConfiguration.redirectUri`: Required by the create-application endpoint, which needs at least one of `openid4vpConfiguration` or `dcApiConfiguration`. In-person proximity verification does not use this redirect, so any valid custom-scheme URI is accepted here. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", // [!code focus] "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. SDK Tethering is currently not required for the React Native Verifier SDK. ### Initialize the SDK with platform configuration [#initialize-the-sdk-with-platform-configuration] When you initialize the SDK, you must provide a `PlatformConfiguration` object with your tenant host and the `id` of the Verifier Application you created. This allows the SDK to register the app instance with your tenant and obtain a license to operate. Initialize the SDK with your platform configuration. The `initialize` method is asynchronous, so call it from an asynchronous context: ```swift title="Initialization" let platformConfig = PlatformConfiguration( tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, applicationId: "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) try await MobileCredentialVerifier.shared.initialize( platformConfiguration: platformConfig ) ``` * `tenantHost`: The URL of your MATTR VII tenant. This must be the tenant where your iOS Verifier Application is configured. * `applicationId`: The `id` of your configured iOS Verifier Application. Initialize the SDK with your platform configuration: ```kotlin title="Initialization" val platformConfig = PlatformConfiguration( tenantHost = URL("https://your-tenant.vii.mattr.global"), applicationId = "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) MobileCredentialVerifier.initialize(context, platformConfig) ``` * `tenantHost`: The URL of your MATTR VII tenant where your Android Verifier Application is configured. * `applicationId`: The `id` of your configured Android Verifier Application. SDK Tethering is currently not required for the React Native Verifier SDK. Once your Verifier Application configurations are created, your application will be able to use the SDK and interact with the MATTR VII platform (for example, to verify credential presentations). ## Managing application instances [#managing-application-instances] Once your Verifier Application is configured and the SDK is initialized, each device that launches your app registers as a new application instance on your tethered MATTR VII tenant. You can view and manage these instances via the MATTR VII API. ### Retrieve all registered instances [#retrieve-all-registered-instances] To view all registered instances for a Verifier Application and track usage: ```http title="Request" GET /v2/presentations/applications/{applicationId}/instances ``` * `applicationId` : The `id` of the Verifier Application you want to inspect. The response includes a paginated list of all registered instances: ```json title="Response" { "data": [ { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "appAttestationType": "app_attestation", "registeredAt": "2023-10-05T14:48:00.000Z", "licenseExpiresAt": "2024-10-05T14:48:00.000Z", "lastAttestedAt": "2023-12-01T10:30:00.000Z", "externalReferenceId": "external-ref-12345", "deviceDetails": { "deviceModel": "iPhone 12", "deviceMake": "Apple", "osVersion": "iOS 14.4" }, "sdkDetails": { "sdkVersion": "1.2.3" } } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` Each instance includes: * `id` : Unique identifier for the registered instance. * `appAttestationType` : The type of attestation used during registration (`none`, `app_attestation`, or `key_attestation`). * `registeredAt` : When the instance was first registered. * `licenseExpiresAt` : When the instance's license expires (the Verifier SDK will automatically handle license renewal). * `lastAttestedAt` : When the instance was last attested. * `deviceDetails` : Information about the device (model, make, OS version). * `sdkDetails` : Information about the SDK version used by the instance. This is useful for tracking how many devices are actively using your application and monitoring usage quotas. ### Delete a specific instance [#delete-a-specific-instance] To remove a specific registered instance: ```http title="Request" DELETE /v2/presentations/applications/{applicationId}/instances/{instanceId} ``` * `applicationId` : The `id` of the Verifier Application. * `instanceId` : The `id` of the specific instance to delete. Once deleted, the instance can no longer interact with the platform or receive tokens, and any existing tokens are revoked. Deleting instances is primarily useful during **testing** when you have a limited number of devices and need to re-register a fresh instance (for example, to test the initial registration flow again). In production, there is nothing preventing the application from requesting another token on the next launch, which would create a new instance — so deleting instances is not an effective way to block a device. ## Attestation vs Assertion fall-back [#attestation-vs-assertion-fall-back] When configuring a Verifier Application, you control whether your MATTR VII tenant requires **attestation** (hardware-backed proof of app integrity) or also accepts a lighter-weight **assertion** (a cryptographic signature proving key possession) during instance registration and token renewal. Each platform has an attestation configuration with a `required` boolean: * When `required` is `true`, the app instance must provide a valid attestation during registration and token renewal. * When `required` is `false`, your tenant also accepts an assertion when an attestation is not available. The SDK handles this automatically. It always attempts to provide an attestation, and falls back to an assertion if it cannot generate one (for example, when the platform attestation service is temporarily unavailable). Your tenant then accepts or rejects the request based on the `required` setting. Your application does not need to manage attestation or assertion details directly. ### When to use each setting [#when-to-use-each-setting] | Scenario | Recommended setting | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | **Production apps in distribution** | `required: true`: Provides the strongest integrity guarantees by verifying the app and device through OS-level attestation. | | **Development and testing** | `required: false`: Useful when running on simulators or devices where attestation services are unavailable. | | **Broad device compatibility** | `required: false`: Some older devices may not support hardware attestation. The assertion fall-back ensures these devices can still register. | Setting attestation to `required: false` reduces the security guarantees of the tethering process. Only use this setting when you have a specific need, such as supporting older devices or during development. # Preview Terms of Use (archived) URL: /docs/resources/terms/archive/preview-terms-of-use > This document is provided for archival purposes only. Last Updated: 15 September 2020 These terms of use (“**Terms**”) apply to your preview use of the MATTR Platform, as described in section 1 below (“**Platform**”). These Terms are agreed between MATTR Limited (“**we**”, “**us**”, or “**our**”) and you or the entity you represent, as applicable (“**you**” or “**your**”). By clicking the “I Agree” button presented alongside these Terms, or using any part of the Platform, you are entering into a binding contract with us for your access to the Platform on the basis of these Terms. If you’re accepting these terms on behalf of a company, or another legal entity, you represent and warrant to us that you have the authority to agree and accept these Terms on behalf of that entity. 1. **Platform.** The Platform is a hosted Platform as a Service (“**PaaS**”). We make the Platform available to you under these Terms as a hosted service. The Platform comprises the following:For the purpose of this subsection, associated application programming interface (“**APIs**”) refers to the endpoints exposed by your tenant on our platform. 1. **Platform Core.** Platform Core includes the following capabilities and their associated APIs: 1. Verifiable Credentials; 2. Verifiable Presentations; 3. Decentralised Identifier; 4. Secure Messaging; 2. **Platform Extensions** 3. **Platform Drivers** 4. **Other auxiliary capabilities** we may make available from time to time in our discretion (e.g. reporting and configuration). 5. **Mobile Wallet.** Mobile Wallet is an application for users to store, discover and present digital credentials. 6. **Other modules and features.** The Platform is being actively developed so we may add new modules and features during the Term. If we make new modules and features available to you, these Terms will apply to those modules and features. 7. **Materials.** We may make available from time to time in our discretion certain software (“**Software**”), documentation and other materials relating to the Platform (together, the “**Materials**”), including: 1. MATTR Learn assets and documentation; 2. system documentation, API specifications, integration guides, system architecture, troubleshooting, installation, configuration manuals and other technical documentation; 3. user documentation in the form of onboarding, user guides, tutorials and operational or process guides; 4. testing, integration testing and prototyping environments; and 5. supporting developer information such as Software Development Kit (SDK), source code examples, supporting operational tools or frameworks. 2. **Usage limitations.** You are only permitted to use the Platform on a preview basis and for a limited time (and functionality may be limited). Access to the Platform under these Terms is provided free of charge, subject to your compliance with the following usage limitations: 1. you may issue or verify a combined maximum of 200 credentials per calendar month; 2. we’ll assign you with non-customisable domains only; and 3. you can only use non-production Decentralised Identifiers (“**DIDs**”) that don’t include personal information of identifiable individuals.If you are interested in production usage, please contact us to learn more about our Enterprise Plan. 3. **Use of the Platform.** 1. **Licence and use.** During the Term, subject to your compliance with these Terms, and for the sole purpose of your internal testing and evaluation: 1. we grant you a non-exclusive, non-transferable licence to use the Materials (including any Software in machine-executable object code form only); and 2. you may access and use the Platform. 2. **Restrictions and limitations.** You may use the Platform only in accordance with the usage limitations and restrictions described in sections 2 and 4, and you must not use live data with the Platform or otherwise use the Platform for production purposes. 3. **Use of data.** You are solely responsible for any and all data that you use with the Platform, including backing up that data. 4. **Restrictions.** You must not: 1. use the Platform in a way prohibited by any applicable laws, rules, regulations, directives, and binding guidance of any governmental authority in any jurisdiction where you are using the Platform; 2. use the Platform in any way that: 1. violates others’ rights; 2. gains or attempts to gain unauthorised access to or disrupt any service, device, account, or network; 3. distributes unsolicited email or malware; or 4. could harm the Platform or impair anyone else’s use of it; 3. copy or modify the Platform, including any Materials except as expressly permitted under these Terms or with our prior written consent in each instance; 4. reverse assemble, reverse engineer, decompile or otherwise attempt to derive source code from the Platform; 5. attempt to work around any technical or licensing limitations in the Platform; 6. sublicense, distribute, sell, lend, rent, lease, transfer, reexport, or grant any rights in or to all or any portion of the Platform; 7. publish or otherwise disclose to any third party any results of any benchmark or other performance tests of the Platform; or 8. remove, alter, or obscure any proprietary rights notices contained in or affixed to the Platform. 5. **Platform updates.** We may make updates to the Platform from time to time, including adding or removing functionality. We reserve the right to make breaking changes to the platform without notification and we will not be responsible for fixing them. 6. **Service levels and support.** Because we are providing you with preview access to the Platform: 1. no Service Level Agreements (“**SLAs**”) apply; 2. the Platform may experience interruptions and downtime (during with you may be unable to access data or functionality); and 3. we will have no obligation to provide support services for the Platform. 7. **Ownership and rights.** 1. **The Platform.** We or our licensors (not you) own and retain all rights, title and interest in and to the Platform (including any additions, modifications and derivative works). We may at any time and at our sole election replace, modify, alter, improve, enhance, or change any part of the Platform. Except for your limited access and use rights expressly set out in these Terms, we reserve all rights in and to the Platform. We don’t grant any additional rights and you are responsible for obtaining any licences to use related or enabling technologies that may be required for you to access and use the Platform. 2. **Third-party materials.** The Platform may include third-party software and materials, which may be licensed or made available subject to additional or alternative terms and conditions. 8. **Term.** These Terms take effect when you click the “I Agree” button presented alongside these Terms or when you first use any part of the Platform, whichever is earliest (“**Effective Date**”) and continue for three months unless earlier cancelled in accordance with section 9 or extended by us at our discretion (“**Term**”). 9. **Suspension and cancellation.** We may suspend or cancel your access to the Platform immediately by written notice and without liability to you if you: 1. breach or otherwise fail to comply with these Terms; or 2. we determine in our sole discretion that such action is reasonably necessary to avoid liability to any person or to ensure compliance with any applicable laws. If we cancel your access to the Platform, this will be considered termination for the purposes of section 8. 10. **Consequences of termination or expiry.** When the Term ends for any reason, you must: 11. immediately stop using the Platform; and 12. within 10 days, return or destroy all copies of the Materials in your possession or control. 13. **Survival.** The end of the Term won’t limit or impact: 14. any rights of a party that have accrued up to and including date on which the Term ends; or 15. sections 4 (Restrictions), 5 (Ownership and rights), 8 (Confidential Information), 9 (Warranties), 10 (Liability) and 11 (Miscellaneous), which survive expiry or termination. 16. **Feedback.** You will promptly notify us by email of any actual or potential error or bug in the Platform. We may, in our sole discretion, elect to correct any errors or bugs confirmed by us to exist in the Platform. You may submit comments, suggestions, feedback, feature requests or ideas about the Platform (“**Feedback**”). You agree to make no claim of ownership of the Feedback or any intellectual property rights that may subsist in it, and we are free to use the Feedback without any additional compensation to you, including disclosing the Feedback to anyone on a confidential or non-confidential basis. We are free to ignore, incorporate, use, disclose, reproduce, license, distribute, modify, perform, display, and otherwise exploit any Feedback, at our sole discretion, without any restriction of any kind (whether due to intellectual property rights or otherwise) and without payment to you. You may only provide Feedback to us that you have the right to provide. 17. **Confidential Information.** 18. **Definition of Confidential Information.** In these Terms, “**Confidential Information**” means any information disclosed by one party (“**Disclosing Party**”) to the other (“**Receiving Party**”), whether before or after the Effective Date, that: 1. is in written, graphic, machine readable or other tangible form and is marked “Confidential”, “Proprietary” or in some other manner to indicate its confidential nature; 2. should be reasonably understood by Receiving Party to be the confidential or proprietary information of Disclosing Party; or 3. that is oral information disclosed by Disclosing Party to Receiving Party, provided that such information is designated as confidential at the time of disclosure and is reduced to writing by Disclosing Party within a reasonable time after its oral disclosure, and such writing is marked in a manner to indicate its confidential nature and delivered to Receiving Party. For clarity, the Platform is our Confidential Information. 19. **Obligation of confidentiality.** Subject to section 12, Receiving Party must: 1. treat as confidential all Confidential Information of Disclosing Party; 2. not use such Confidential Information except to exercise its rights and perform its obligations under these Terms, 3. not disclose such Confidential Information to any third party 4. use at least the same degree of care it uses to prevent the disclosure of its own confidential information of like importance, to prevent the disclosure of Confidential Information of Disclosing Party. 5. promptly notify Disclosing Party of any actual or suspected misuse or unauthorised disclosure of Disclosing Party’s Confidential Information. 20. **Exception.** Each party’s obligation to maintain confidentiality shall not apply to any information that: 1. is, or becomes, available to the public through no fault or breach by the Receiving Party; 2. was in the Receiving Party’s possession prior to disclosure by the Disclosing Party; 3. is disclosed to the Receiving Party by a third party having, to the best of the Receiving Party’s knowledge, the right to make such disclosure; or 4. is independently developed by the Receiving Party without reference to the Disclosing Party’s Confidential Information. 21. **WARRANTIES.** 22. THE PLATFORM IS MADE AVAILABLE BY US ON AN “AS IS”, “WITH ALL FAULTS” AND “AS AVAILABLE” BASIS. TO THE MAXIMUM EXTENT PERMITTED BY LAW, WE MAKE NO WARRANTIES, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE WITH RESPECT TO THE PLATFORM OR THE USE OR OPERATION THEREOF, AND SPECIFICALLY DISCLAIM THE IMPLIED WARRANTIES OF MERCHANTABILITY, ACCURACY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. WE DO NOT WARRANT THAT THE PLATFORM WILL FUNCTION AS DESCRIBED OR BE ERROR-FREE, SECURE, RELIABLE OR ACCURATE. NO ORAL OR WRITTEN STATEMENT MADE TO YOU IN RELATION TO THE PLATFORM SHALL CREATE ANY WARRANTY THAT HAS BEEN EXPRESSLY DISCLAIMED IN THESE TERMS. 23. WE ASSUME NO RISK OR LIABILITY IN YOUR USE OF THE PLATFORM. YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING THE PLATFORM AND ASSUME ALL RISKS ASSOCIATED WITH USING THIS VERSION OF THE PLATFORM, INCLUDING RISKS AND COSTS OF PLATFORM ERRORS, COMPLIANCE WITH APPLICABLE LAWS, DAMAGE TO OR LOSS OF DATA, PROGRAMS OR EQUIPMENT, AND UNAVAILABILITY OR INTERRUPTION OF OPERATIONS. 24. **LIABILITY.** TO THE MAXIMUM EXTENT PERMITTED BY LAW, IN NO EVENT WILL WE BE LIABLE FOR ANY LOSSES, LIABILITIES, DAMAGES OR EXPENSES, INCLUDING LOSS OF DATA, LOSS OF SYSTEM AVAILABILITY, LOSS OF COMPUTER RUN TIME, BUSINESS INTERRUPTION, LOST PROFITS, LOST REVENUE, LOST GOODWILL, LOST BUSINESS, ANTICIPATED SAVINGS, COST OF COVER OR OTHER SPECIAL, INCIDENTAL, CONSEQUENTIAL, DIRECT OR INDIRECT DAMAGES ARISING FROM THE USE OF THE PLATFORM OR ACCOMPANYING MATERIALS, HOWEVER CAUSED AND WHETHER BASED IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR ANY OTHER THEORY OF LIABILITY. THIS LIMITATION WILL APPLY EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. THE PARTIES ACKNOWLEDGE THAT THIS IS A REASONABLE ALLOCATION OF RISK. 25. **Indemnity.** You will defend, hold harmless and indemnify us from and against any claim or action brought by a third party (including all damages, liabilities, costs and expenses, and lawyers’ fees on a solicitor and own-client basis) arising out of or in connection with your breach of these Terms or infringement of our intellectual property. 26. **Miscellaneous.** You may not assign, sublicense or otherwise transfer the rights or licence granted under these Terms, by agreement or by operation of law, without our prior written consent, and all assignments in violation of this prohibition will be null and void. These Terms amount to the entire agreement between you and us relating to its subject matter. This Agreement will be governed by the laws of New Zealand without reference to conflict of law principles. In any dispute arising out of this Agreement, you consent to the jurisdiction of New Zealand courts and agree to bring any actions arising out of these Terms in such court. Any delay or failure by us to exercise a right or remedy will not result in a waiver of that, or any other, right or remedy. Where the term “include” or its grammatical variations appear, this does not limit the text that follows. If any part of these Terms is held unenforceable, the remainder of these Terms will continue in full force and effect. 27. **Updates.** We may update or replace these Terms at any time by publishing a new version on our website. By using the Platform after the “Last updated” date at the top of these Terms, you are deemed to have accepted the updated Terms. If you don’t agree to the updated Terms, you must stop using the Platform immediately. # Privacy Policy (archived) URL: /docs/resources/terms/archive/website-privacy-policy-legacy > This document is provided for archival purposes only. Last Updated: 30 November 2020 MATTR is built and operated as a privacy-first company. Enhancing privacy and trust in digital transactions is part of our DNA. This privacy policy explains how we collect, use and disclose data that relates to you, and applies to your use of our Website and the Preview Platform, (collectively, the **Services**). We have a separate privacy policy for the Preview Digital Wallet, which is currently available by invite only. When we say “**our**”, “**we**”, or “**us**”, we mean MATTR Limited (**MATTR**). When we say “**you**” or “**your**” we mean you or the entity you represent, as applicable. By using our Services, you acknowledge that you have read and accepted our privacy policy. We may update this privacy policy from time to time by publishing the updated version on our Website. We will update the “Last Updated” date at the top of this page, and your use of our Services after that date confirms your acceptance of the updated policy. As a New Zealand-based company, we comply with the New Zealand Privacy Act (**Act**) when dealing with your information. This policy does not limit or exclude any of your rights under the Act. You can find more information about the Act and your rights at [www.privacy.org.nz](https://privacy.org.nz/). #### How we collect personal data [#how-we-collect-personal-data] We only ask for your information where we actually need it. When you use our Services, we ask you to provide certain personally identifiable information **(Personal Data)** which we may use to identify and contact you. Examples of Personal Data we ask for when you use certain aspects of the Services (e.g. if you sign up to use our Preview Services) include your name, email address, and organisation. In situations where you can’t be identified (for example, because the information is aggregated and anonymised) then that is not Personal Data. #### How we use the personal data we collect [#how-we-use-the-personal-data-we-collect] We use the Personal Data that we collect when you use our Services to: * provide, update and maintain the Services, * secure, troubleshoot, and provide support, * notify you about changes, * respond to you if you make an inquiry, * if you opt in, to send you materials and updates about our business, * analyse, improve and develop the functionality of our Services, * prevent, detect, or investigate security concerns, including fraud, * comply with applicable laws, regulations, and industry requirements, and * if not captured above, fulfil a purpose that you have expressly requested from us or consented to. #### Disclosure of your personal data [#disclosure-of-your-personal-data] We ***do not*** sell your personal data or share it without your consent. We will only disclose your Personal Data: * if required to comply with applicable laws, regulations, or legal processes, * to prevent, detect, or investigate security concerns, including fraud * to the extent it is necessary, to partners that we engage to provide aspects of the Services. #### Protecting your personal information [#protecting-your-personal-information] We will take reasonable steps to protect your Personal Data from loss, and unauthorised disclosure and use. If a privacy breach occurs, we will endeavour to communicate with you and all relevant parties as soon as possible, and report all serious incidents to the Privacy Commissioner. We will take appropriate steps to minimise the harm of the incident. #### Accessing and correcting your personal information [#accessing-and-correcting-your-personal-information] You are entitled to access the Personal Data that we hold (provided it is readily retrievable) and to request a correction. You may also ask us to confirm whether we hold particular information. These rights may be subject to certain conditions or grounds for refusal, as set out in the Act. If you want to exercise any of the above rights, please email us at [privacy@mattr.global](mailto:privacy@mattr.global). You will need to confirm your identity and set out the details of your request (eg. the Personal Data you would like to access, or the correction you are requesting). #### How long we keep your personal information [#how-long-we-keep-your-personal-information] We will retain your Personal Data only for as long as is necessary (for the purposes for which it was collected) and as otherwise required by law. #### Cookies [#cookies] A cookie is a small text file that websites can ask your browser to store on your device to enhance the customer experience. We use a small number of cookies for the purpose of storing preferences. We do not use any cookies for the purposes of marketing. You can disable cookies by changing the settings on your browser, which may impact your experience of our Website. #### Preventing bots and spam [#preventing-bots-and-spam] Our Website is protected by the hCaptcha anti-bot service. This checks whether the data entered on our Website (such as on our sign-up form for Preview Services) has been entered by a human or by an automated program. To learn more you can read hCaptcha’s [privacy policy](https://www.hcaptcha.com/privacy) and [terms of service](https://www.hcaptcha.com/privacy). #### Privacy-focused analytics [#privacy-focused-analytics] We do not use Google Analytics. We’ve made the conscious choice to pay for a privacy-first analytics tool called Fathom. You can learn more about privacy-focused analytics [here](https://usefathom.com/privacy-focused-web-analytics), and read Fathom’s privacy policy [here](https://usefathom.com/privacy). #### Security of Data [#security-of-data] The security of your data is very important to us – but please remember that no method of transmission over the Internet or method of electronic storage is 100% secure. While we strive to use commercially acceptable means to protect your Personal Data, we cannot guarantee its absolute security. #### Links to other websites [#links-to-other-websites] If you follow a link from our Website to a website hosted by a different entity, that website will be subject to a different privacy policy and terms of service, which we recommend that you review. #### Privacy queries [#privacy-queries] If you have any queries about this privacy policy, you can contact us at any time at [privacy@mattr.global](mailto:privacy@mattr.global). # MATTR Website Terms of Use (archived) URL: /docs/resources/terms/archive/website-terms-of-use-legacy > This document is provided for archival purposes only. Last Updated: 15 September 2020 Welcome to the MATTR website! These Website Terms of Use (“**Terms**“) are agreed between MATTR Limited (“**we**”, “**us**”, or “**our**”) and you or the entity you represent, as applicable (“**you**” or “**your**”) and apply only to your use of our Website. Different terms apply to your use of our Services (“**Services**“). By using our Website, you acknowledge that you have read and accepted these Terms. We may update or replace these Terms from time to time by publishing a new version on our Website. By using the Website after the “Last updated” date at the top of these Terms, you are deemed to have accepted the updated Terms. If you don’t agree to the updated Terms, you must stop using the Website. #### Your use of our Website [#your-use-of-our-website] The information provided in our Website is intended only as an introduction and guide to our range of available Services. You must not use our Website or its contents: * in breach of any law or for any unlawful act, or * to damage or disrupt our Website, Services or any other service or website #### Liability [#liability] To the maximum extent permitted by law: * our Website and the information presented on it is provided on an “as-is” and “as-available” basis without any warranties, representations, or guarantees of any kind (whether, express, implied, statutory or otherwise) including, but not limited to, warranties of non-infringement, merchantability, or fitness for a particular purpose, and * neither we nor our suppliers will be liable under the law of tort (including negligence), contract or otherwise for any loss of income, profits, data, business opportunity or savings or for any indirect, incidental, consequential, exemplary, punitive or special loss or damage of any person, however caused, arising out of or in connection with your  use of our Website or reliance on any information on our Website. #### Intellectual property [#intellectual-property] We or our licensors (as applicable) own all copyright and all other intellectual property rights that may subsist in this Website or any content presented on this Website (and we reserve all rights in relation to such material). You may use this Website for non-commercial purposes only, and you must: * not remove any copyright, trademark and other proprietary notices contained in the content * not copy, display, redistribute or otherwise use for commercial purposes any portion of this site without our prior written permission, and * include an attribution to MATTR Limited if you use any information contained on our Website The trademarks appearing on our Website belong to us, our suppliers, or our licensors as applicable. You must not use or reproduce or allow anyone to use or reproduce those trademarks for any reason without prior written permission. #### Feedback and unsolicited submissions [#feedback-and-unsolicited-submissions] If you give us feedback or submit ideas about our Website or Services (**“Ideas”**), you: * grant us the right to use those Ideas to improve our Website and our products and services (and for any other purpose we deem necessary or desirable) without being obliged to seek your permission or pay you any compensation in respect of our use of those Ideas, and * waive any moral rights in those Ideas and consent to us freely using those Ideas without further consent or attribution to you. #### Third party sites [#third-party-sites] Our Website may include links to external sites. These sites are not under our control and we are not responsible for, and we make no representations or warranties concerning the contents of any such external site. Such links are provided to you only as a convenience, and the inclusion of any link does not imply endorsement, verification, or certification by us of the linked site. #### Jurisdiction [#jurisdiction] Our Website and these Terms are governed by the laws of New Zealand and the courts of New Zealand will have non-exclusive jurisdiction to hear and determine any dispute arising in relation to them. # MATTR Customer Agreement (archived) URL: /docs/resources/terms/customer-agreement/20-4-22 This document is provided for archival purposes only. Last Updated: 20 April 2022 [See what's changed](/docs/resources/terms/customer-agreement/recent-changes) | [Previous versions](#previous-versions) This Agreement sets out the terms and conditions governing your access to and use of the Services and Materials (as defined below). It is an agreement between MATTR Limited (**we**, **us**, or **our**) and you or the entity you represent (**you** or **your**). This Agreement takes effect when you click an “I Accept” button or tick box presented alongside these terms, or when you otherwise access or use any of the Services or Materials (**Effective Date**). By entering into this Agreement, you warrant and represent that: 1. you are lawfully able to enter into contracts (e.g. you are not a minor); and 2. if you are entering into this Agreement for an entity (e.g. a company, government agency or other organisation), you are authorised to do so and have legal authority to bind that entity. ## Previous versions [#previous-versions] [MATTR Customer Agreement - 25 March 2021 (archived)](/docs/resources/terms/customer-agreement/25-3-21) # MATTR Customer Agreement (archived) URL: /docs/resources/terms/customer-agreement/25-3-21 This document is provided for archival purposes only. Last Updated: 25 March 2021[](#previous-versions) This Agreement sets out the terms and conditions governing your access to and use of the Services and Materials (as defined below). It is an agreement between MATTR Limited (**we**, **us**, or **our**) and you or the entity you represent (**you** or **your**). This Agreement takes effect when you click an “I Accept” button or tick box presented alongside these terms, or when you otherwise access or use any of the Services or Materials (**Effective Date**). By entering into this Agreement, you warrant and represent that: 1. you are lawfully able to enter into contracts (e.g. you are not a minor); and 2. if you are entering into this Agreement for an entity (e.g. a company, government agency or other organisation), you are authorised to do so and have legal authority to bind that entity. # MATTR Customer Agreement (archived) URL: /docs/resources/terms/customer-agreement/30-6-22 This document is provided for archival purposes only. Last Updated: 30 June 2022 [See what's changed](/docs/resources/terms/customer-agreement/recent-changes) | [Previous versions](#previous-versions) This Agreement sets out the terms and conditions governing your access to and use of the Services and Materials (as defined below). It is an agreement between MATTR Limited (**we**, **us**, or **our**) and you or the entity you represent ( **you** or **your**). This Agreement takes effect when you click an “I Accept” button or tick box presented alongside these terms, or when you otherwise access or use any of the Services or Materials (**Effective Date**). By entering into this Agreement, you warrant and represent that: 1. you are lawfully able to enter into contracts (e.g. you are not a minor); and 2. if you are entering into this Agreement for an entity (e.g. a company, government agency or other organisation), you are authorised to do so and have legal authority to bind that entity. ## Previous versions [#previous-versions] [MATTR Customer Agreement - 20 April 2022 (archived)](/docs/resources/terms/customer-agreement/20-4-22) [MATTR Customer Agreement - 25 March 2021 (archived)](/docs/resources/terms/customer-agreement/25-3-21) # MATTR Customer Agreement URL: /docs/resources/terms/customer-agreement Last Updated: 21 April 2026 [See what's changed](/docs/resources/terms/customer-agreement/recent-changes) | [Previous versions](#previous-versions) This Agreement sets out the terms and conditions governing your access to and use of the Services and Materials (as defined below). It is an agreement between MATTR Limited (we, us, or our) and you or the entity you represent (you or your). This Agreement takes effect when you click an “I Accept” button or tick box presented alongside these terms, or when you otherwise access or use any of the Services or Materials (Effective Date). By entering into this Agreement, you warrant and represent that: 1. you are lawfully able to enter into contracts (e.g. you are not a minor); and 2. if you are entering into this Agreement for an entity (e.g. a company, government agency or other organisation), you are authorised to do so and have legal authority to bind that entity. ## Previous versions [#previous-versions] * [MATTR Customer Agreement - 30 June 2022 (archived)](/docs/resources/terms/customer-agreement/30-6-22) * [MATTR Customer Agreement - 20 April 2022 (archived)](/docs/resources/terms/customer-agreement/20-4-22) * [MATTR Customer Agreement - 25 March 2021 (archived)](/docs/resources/terms/customer-agreement/25-3-21) # MATTR Customer Agreement - What's Changed URL: /docs/resources/terms/customer-agreement/recent-changes ## Changes posted April 21, 2026 [#changes-posted-april-21-2026] MATTR updated the [MATTR Customer Agreement](/docs/resources/terms/customer-agreement) on April 21, 2026. This is a comprehensive update reflecting MATTR's evolution from a self-service platform model to an enterprise-focused, Order Form-based commercial relationship. The update reflects the inclusion of a wide range of additional protections for Customers that support its use for commercial orders and use in production. At a glance, here are the key changes: 1. **New agreement structure.** The agreement has been reorganised and expanded from 20 to 21 clauses. Definitions have moved to the front (clause 1), and a new "Agreement" clause (clause 2) describes how Order Forms, Service Terms, Support Terms and Data Processing Terms fit together in a clear order of priority. 2. **Order Form-based model.** The agreement now caters for individually negotiated Order Forms that specify Services, Fees, Terms, Regions and other commercial details, replacing the previous self-service, pay-as-you-go approach. 3. **Expanded definitions.** Many new defined terms have been introduced to support the broader scope of services, including Agent, Affiliate, Customer Application User, Customer Representative, Holder, Issuer, MATTR User, Verifier, Service Terms, Support Terms, Specification, and Third Party Credential Capability Provider. The former "Account Information" concept has been replaced by "Customer Representative Information" with a clearer scope. 4. **Supply commitments and warranties.** For commercial orders, MATTR now commits to a number of service warranties and commitments, including that each Service will substantially comply with its Specification (clause 17.1), and obligations to comply with Good Industry Practice, use reasonable care and skill, and comply with applicable Laws. 5. **Dedicated confidentiality section.** Confidentiality commitments by MATTR are now included in a standalone clause (clause 8) with detailed provisions for protection, return or destruction, retention rights, publicity and customer reference rights, replacing a single paragraph in the former General section. 6. **Enhanced data provisions.** New provisions cover data sub-processors (with a 30-day change notification process), data minimisation principles for cross-Region transfers, Derived Data and anonymised statistical data usage, and more detailed data breach notification obligations. 7. **Trial Access.** The former brief trial period clause has been replaced with a detailed Trial Access provision (clause 9.3) that clearly sets out reduced service commitments applicable to free trial access (e.g. when you sign up on our website). 8. **Liability and indemnity.** The liability framework has been expanded to reflect common customer requests in commercial negotiations, including a specific list of matters excluded from the liability cap (clause 18.2), and an enhanced liability for breach of confidentiality and data obligations via a new super cap of three times the Fees payable in the preceding 12 months. 9. **Arbitration.** Dispute resolution has moved from the New Zealand Arbitration Act 1996 to the Singapore International Arbitration Centre (SIAC) rules, while retaining Auckland, New Zealand as the seat of arbitration. 10. **New general provisions.** Several new clauses have been added, including a no-poaching obligation (clause 21.1), a Material Adverse Change clause enabling renegotiation or termination in response to significant regulatory or government changes (clause 21.10), a non-exclusivity clause (clause 21.11), and a clause limiting claims to MATTR directly (clause 21.9). 11. **Terminology updates.** "Related companies" has been replaced with "Affiliates" throughout, and "Personnel" now formally covers agents, officers, employees and contractors. By continuing to use the Services or Materials 31 days after the effective date of these and any changes to the MATTR Customer Agreement, you agree to be bound by the modified terms. Previous versions are available for reference, and archival purposes. ## Changes posted 30 Jun 2022 [#changes-posted-30-jun-2022] MATTR updated the [MATTR Customer Agreement](/docs/resources/terms/customer-agreement) on 30 Jun 2022. We've updated our Customer Agreement so that it covers everything it needs to, because it's important we're all on the same page. The changes won’t affect the way you use MATTR VII or our SDK platform. They’ll make it easier for you to understand what to expect from MATTR - and what is controlled by other parties. At a glance, here are the key changes: 1. We've made it more obvious that access is required to your Google Pay / Apple Wallet merchant credentials, to support the creation of compliant passes. 2. We've clarified that when you create Google Pay / Apple Wallet passes, you'll need to ensure they align with Google and Apple’s respective licence/developer terms. If a pass created doesn't align with those, it may mean that you are no longer upholding your end of the agreement we have together. By continuing to use the Services or Materials 31 days after the effective date of these and any changes to the MATTR Customer Agreement, you agree to be bound by the modified terms. Previous versions are available for reference, and archival purposes. # MATTR Data Processing Terms URL: /docs/resources/terms/data-processing-terms Last Updated: 25 March 2021 [See what's changed](/docs/resources/terms/data-processing-terms/recent-changes) | [Previous versions](#previous-versions) These Data Processing Terms apply to the Services supplied by MATTR Limited if you or your End Users access or use the Services in the European Economic Area or are otherwise subject to the GDPR or legislation implementing the GDPR. The Data Processing Terms set out terms that apply to the Processing of your Personal Data and Personal Data of your End Users by MATTR as set out in clause 5.2 of the MATTR Customer Agreement. In these Data Processing Terms: * “**Data Subject”** means your End Users, employees, contractors and agents, who in each case are natural persons to the extent that they are identified or identifiable and, if you are a natural person, you; * “**Data Controller**” means you (the party that has entered into the MATTR Customer Agreement); * “**Data Processor**” means MATTR Limited; and * other capitalised terms which are not defined in the Data Processing Terms have the meanings set out in the MATTR Customer Agreement. To provide the Services to the Data Controller, the Data Processor will store and Process Personal Data of Data Subjects in accordance with these MATTR Data Processing Terms. Where there is any conflict or ambiguity between these Data Processing Terms and the MATTR Customer Agreement, these Data Processing Terms take priority. # MATTR Data Processing Terms - What's Changed URL: /docs/resources/terms/data-processing-terms/recent-changes Changes posted March 25, 2021 MATTR updated the [MATTR Data Processing Terms](/docs/resources/terms/data-processing-terms) on March 25, 2021. The [MATTR Data Processing Terms](/docs/resources/terms/data-processing-terms) were published. By continuing to use the Services or Materials after the effective date of any changes to the MATTR Data Processing Terms, you agree to be bound by the modified terms. # MATTR Data Sub-processors (archived) URL: /docs/resources/terms/data-sub-processors/10-6-25 This document is provided for archival purposes only. Last Updated: 10 June 2025 [See what's changed](/docs/resources/terms/data-sub-processors/recent-changes) | [Previous versions](#previous-versions) A data sub-processor is a service or capability hosted outside the core MATTR cloud environment that may be used to process Customer Data, such as configuration data, logs, or master data. Alternatively, it may be a capability employed to secure an environment where Customer Data is stored. MATTR may engage the following entities to carry out specific data sub-processing activities: | Entity | Activity | Organization location | | :----------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------- | | Amazon Web Services, Inc. | Amazon Web Services (AWS) provides cloud infrastructure services from data centres based in a region agreed with MATTR customers. MATTR also uses other AWS products and services. | United States | | Atlassian Pty Ltd | JIRA service desk is used for logging and managing incidents and service requests (which may contain come Customer data to support incident investigation). | Australia | | DataDog | Datadog is used for monitoring the operational health of MATTR’s platforms. | United States | | Doppler | Doppler is used for configuration management of MATTR GO Applications. | United States | | GitHub | GitHub is used during the onboarding and access provisioning to MATTR platforms and applications. | United States | | InnoCraft Limited (Matomo Analytics) | Matomo is used for website analytics tracking on MATTR websites. | Germany | | Jotform | Jotform is used for form submission on MATTR websites. | United States | | MAKE (previously Integromat) | MAKE is used for the provisioning of trial accounts. | Czech Republic | | Microsoft (Office 365) | Office365 is a collaboration tool used for communication with customers and may store customer specific documentation. | United States | | Microsoft (EntraID) | EntraID is the Identity provider for MATTR employees single sign-on across internal MATTR enterprise systems and cloud infrastructure. | Australia | | Okta, Inc. | Okta is the Identity provider used to manage access to APIs and user accounts for access to the Self Service Portal. Deployed in the same (or adjacent) region to the MATTR cloud capability. | United States | | Pardot (Salesforce Marketing Cloud) | Pardot is used for marketing automation and customer onboarding assistance. | United States | | Salesforce, Inc. | Salesforce.com is used for sales and opportunity management and may store information about MATTR customers, prospective MATTR customers and Trial users. | United States | | Sentry | Sentry is an error monitoring platform used by MATTR GO Mobile Applications. | United States | | Slack Technologies, Inc. | Slack is a collaboration tool used for communication with customers and MATTR product support. | United States | | Spark New Zealand Limited | Spark provides a Security Operations Centre (SOC) and Security Information and Event Management (SIEM) services. | New Zealand | | Stripe, Inc. | Stripe supports online payments for MATTR customers. | United States | | Sumo Logic, Inc. | Sumo Logic is a cloud-based log management capability used for audit and monitoring of MATTR’s capabilities. Deployed in the same (or adjacent) region to the MATTR cloud capability. | United States | | Webflow | Webflow is a cloud-based development platform used by the MATTR website. | United States | | Xero Limited | Xero is MATTR’s accounting software. | New Zealand | | 6Sense | 6Sense is used for website analytics and sales and opportunity management. | United States | For more information regarding data sub-processors (including more detailed information regarding the type of data processed and the storage location) please contact [privacy@mattr.global](mailto:privacy@mattr.global). ## Previous versions [#previous-versions] * [8 Nov 2024](/docs/resources/terms/data-sub-processors/8-11-24) * [28 May 2024](/docs/resources/terms/data-sub-processors/28-5-24) * [25 Mar 2024](/docs/resources/terms/data-sub-processors/25-3-24) * [16 May 2023](/docs/resources/terms/data-sub-processors/16-5-23) # MATTR Data Sub-processors URL: /docs/resources/terms/data-sub-processors/15-4-26 This document is provided for archival purposes only. Last Updated: 15 April 2026 [See what's changed](/docs/resources/terms/data-sub-processors/recent-changes) | [Previous versions](#previous-versions) A data sub-processor is a service or capability hosted outside the core MATTR cloud environment that may be used to process Customer Data, such as configuration data, logs, or master data. Alternatively, it may be a capability employed to secure an environment where Customer Data is stored. MATTR may engage the following entities to carry out specific data sub-processing activities: ## MATTR as a Data Processor [#mattr-as-a-data-processor] | Data Sub Processor | Data Processed | Data location | Relevant MATTR Products | | :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------ | | **Amazon Web Services, Inc.** | Amazon Web Services (AWS) provides cloud infrastructure services from data centres based in a region agreed with MATTR customers. MATTR also uses other AWS products and services. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | | **Atlassian Pty Ltd** | JIRA service desk is used for logging and managing incidents and service requests (which may contain some Customer data including personal information to support incident investigation). | Australia | MATTR VII
MATTR Pi
MATTR GO | | **DataDog** | Datadog is used for monitoring the operational health of MATTR’s platforms. | United States | MATTR VII
MATTR Pi
MATTR GO | | **Doppler** | Doppler is used for configuration management of MATTR GO mobile applications. | United States | MATTR GO | | **GitHub** | GitHub is used to provide customers with access to specific MATTR capabilities such as SDKs. | United States | MATTR Pi | | **Microsoft (EntraID)** | EntraID is the Identity provider for MATTR employees single sign-on across internal MATTR enterprise systems and cloud infrastructure. | Australia | MATTR VII
MATTR Pi
MATTR GO | | **NPM** | NPM is used to provide customers with access to specific MATTR capabilities such as SDKs. | United States | MATTR Pi | | **Okta, Inc.** | Okta is used as an Authorisation Server for MATTR VII APIs.
Okta is also used as an IDP to support the MATTR Global Control Hub, including the Management APIs (M2M Clients) and MATTR Portal (Users), which are used to oversee and manage access to cloud environments. | The Okta Authorisation Server is Cloud region aligned **where possible**,

The Okta IDP (as part of MATTR Global Control Hub) is based in Australia. | MATTR VII
MATTR Pi
MATTR GO | | **Red Canary** | Red Canary provides Security Operations Centre (SOC) and Managed Detection & Response (MDR) services. | United States | MATTR VII
MATTR Pi
MATTR GO | | **Sentry** | Sentry is an error monitoring platform used by MATTR GO Mobile Applications and the MATTR Portal. | United States | MATTR VII
MATTR GO | | **Sumo Logic, Inc.** | Sumo Logic is a cloud-based log management capability used for audit and monitoring of MATTR’s capabilities. Deployed in the same (or adjacent) region to the MATTR cloud capability. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | ## MATTR as a Data Controller [#mattr-as-a-data-controller] | Data Sub Processor | Data Processed | Data Category | Data Location | Relevant MATTR Products | | :--------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------- | :------------ | :------------------------------------ | | **Docusign** | Used to electronically sign contractual information with MATTR customers | Customer Data - Ancillary | Australia | MATTR customers using Docusign | | **HubSpot** | HubSpot stores and manages information about MATTR's customers for sales and opportunity management, and other operational functions. | Customer Data - Ancillary
Customer Data - Representative Data | Australia | All MATTR customers | | **InnoCraft Limited (Matomo Analytics)** | Matomo is used for website analytics tracking on MATTR websites. | Customer Data - Representative Data | Germany | MATTR customers using MATTR website | | **Microsoft (Office 365)** | Office365 is a collaboration tool used for communication with customers and may store customer-specific documentation. | Customer Data - Ancillary | United States | All MATTR Customers | | **Notion** | Used to store specific information including customer contact information for incident management and other repositories of operational information / designs etc. | Customer Data - Ancillary
Customer Data - Representative Data | United States | All MATTR Customers | | **Slack Technologies, Inc.** | Slack is a collaboration tool used for communication with customers and MATTR product support. Also used internally in the context of Incident Management. | Customer Data
Customer Data - Personal Information | United States | All MATTR Customers | | **Stripe, Inc.** | Stripe supports online payments for MATTR customers. | Customer Data | United States | Customers paying bills by credit card | | **Webflow** | Webflow is a cloud-based development platform used by the MATTR website. | Customer Data - Representative Data | United States | MATTR customers using MATTR website | | **Xero Limited** | Xero is MATTR’s accounting software and is used for invoicing and payments. | Customer Data - Ancillary | New Zealand | All MATTR Customers | For more information regarding data sub-processors (including more detailed information regarding the type of data processed and the storage location) please contact [privacy@mattr.global](mailto:privacy@mattr.global). ## Previous versions [#previous-versions] * [23 Mar 2026](/docs/resources/terms/data-sub-processors/23-3-26) * [2 Feb 2026](/docs/resources/terms/data-sub-processors/2-2-26) * [17 Oct 2025](/docs/resources/terms/data-sub-processors/17-10-25) * [10 June 2025](/docs/resources/terms/data-sub-processors/10-6-25) * [8 Nov 2024](/docs/resources/terms/data-sub-processors/8-11-24) * [28 May 2024](/docs/resources/terms/data-sub-processors/28-5-24) * [25 Mar 2024](/docs/resources/terms/data-sub-processors/25-3-24) * [16 May 2023](/docs/resources/terms/data-sub-processors/16-5-23) # MATTR Data Sub-processors (archived) URL: /docs/resources/terms/data-sub-processors/16-5-23 This document is provided for archival purposes only. Last Updated: 16 May 2023 [See what's changed](/docs/resources/terms/data-sub-processors/recent-changes) | [Previous versions](#previous-versions) MATTR may engage the following entities to carry out specific Personal Data Processing activities: | Entity | Activity | Location | | :----------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | :------------- | | Amazon Web Services, Inc. | Amazon Web Services provides cloud infrastructure services from data centres based in a region agreed with MATTR’s customers. | United States | | Atlassian Pty Ltd | Jira Service Desk is used by MATTR’s customers for logging and tracking of service requests and incidents. | Australia | | InnoCraft Limited (Matomo Analytics) | Matomo may be used for website analytics tracking on MATTR websites. | New Zealand | | MAKE (previously Integromat) | MAKE is used for the provisioning of trial accounts | Czech Republic | | Mezmo, Inc. | Mezmo is a cloud-based log management capability used for audit and monitoring of MATTR’s capabilities. | United States | | Microsoft | Office365 is a collaboration tool used for communication with customers and may store customer specific documents. | United States | | Netlify, Inc. | Netlify is a cloud-based development platform used by the MATTR website and Self-Service capabilities. | United States | | Okta, Inc. | Okta is an Identity provider used to manage access to MATTR APIs and self-service capabilities. | United States | | Pardot (Salesforce Marketing Cloud) | Pardot is used for marketing automation and onboarding assistance. | United States | | Salesforce, Inc. | Salesforce stores and manages information about MATTR’s customers for operational reasons (e.g. customer contacts) | United States | | Slack Technologies, Inc. | Slack is a collaboration tool used for communication with customers and MATTR product support. | United States | | Spark New Zealand Limited | Spark provides a Security Operations Centre (SOC) and Security Information and Event Management (SIEM) services. | New Zealand | | Stripe, Inc. | Stripe supports online payments for MATTR customers. | United States | | Sumo Logic, Inc. | Sumo Logic is a cloud-based log management capability used for audit and monitoring of MATTR’s capabilities. | United States | | Xero Limited | Xero is MATTR’s accounting software. | New Zealand | For more information regarding data sub-processors (including more detailed information regarding the type of data processed and the storage location) please contact [privacy@mattr.global](mailto:privacyx@mattr.global). # MATTR Data Sub-processors URL: /docs/resources/terms/data-sub-processors/17-10-25 This document is provided for archival purposes only. Last Updated: 17 Oct 2025 [See what's changed](/docs/resources/terms/data-sub-processors/recent-changes) | [Previous versions](#previous-versions) A data sub-processor is a service or capability hosted outside the core MATTR cloud environment that may be used to process Customer Data, such as configuration data, logs, or master data. Alternatively, it may be a capability employed to secure an environment where Customer Data is stored. MATTR may engage the following entities to carry out specific data sub-processing activities: ## MATTR as a Data Processor [#mattr-as-a-data-processor] | Data Sub Processor | Data Processed | Data location | Relevant MATTR Products | | :---------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :------------------------------------ | | **Amazon Web Services, Inc.** | Amazon Web Services (AWS) provides cloud infrastructure services from data centres based in a region agreed with MATTR customers. MATTR also uses other AWS products and services. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | | **Atlassian Pty Ltd** | JIRA service desk is used for logging and managing incidents and service requests (which may contain some Customer data including personal information to support incident investigation). | Australia | MATTR VII
MATTR Pi
MATTR GO | | **DataDog** | Datadog is used for monitoring the operational health of MATTR’s platforms. | United States | MATTR VII
MATTR Pi
MATTR GO | | **Doppler** | Doppler is used for configuration management of MATTR GO mobile applications. | United States | MATTR GO | | **GitHub** | GitHub is used to provide customers with access to specific MATTR capabilities such as SDKs | United States | MATTR Pi | | **MATTR Control Plane** | Listed for completeness, MATTR operates a global control plane as a distributed management system, designed to oversee and manage all MATTR regional deployments worldwide. This control plane can be distributed across Australia, New Zealand, The United Kingdom, Canada, the United States or Germany. The global control plane does not store logs, customer data, or any sensitive information. | Australia
Canada
United Kingdom
USA
Germany | MATTR VII
MATTR Pi
MATTR GO | | **Microsoft (EntraID)** | EntraID is the Identity provider for MATTR employees single sign-on across internal MATTR enterprise systems and cloud infrastructure. | Australia | MATTR VII
MATTR Pi
MATTR GO | | **NPM** | NPM is used to provide customers with access to specific MATTR capabilities such as SDKs. | United States | MATTR Pi | | **Okta, Inc.** | Okta is the Identity provider used to manage access to APIs and user accounts (for access to the MATTR Portal). Deployed in the same (or adjacent) region to the MATTR cloud capability. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | | **Sentry** | Sentry is an error monitoring platform used by MATTR GO Mobile Applications. | United States | MATTR GO | | **Spark New Zealand Limited** | Spark provides a Security Operations Centre (SOC) and Security Information and Event Management (SIEM) services. | New Zealand | MATTR VII
MATTR Pi
MATTR GO | | **Sumo Logic, Inc.** | Sumo Logic is a cloud-based log management capability used for audit and monitoring of MATTR’s capabilities. Deployed in the same (or adjacent) region to the MATTR cloud capability. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | ## MATTR as a Data Controller [#mattr-as-a-data-controller] | Data Sub Processor | Data Processed | Data Category | Data Location | Relevant MATTR Products | | :--------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------- | :------------ | :------------------------------------ | | **Docusign** | Used to electronically sign contractual information with MATTR customers | Customer Data - Ancillary | Australia | MATTR customers using Docusign | | **InnoCraft Limited (Matomo Analytics)** | Matomo is used for website analytics tracking on MATTR websites. | Customer Data - Representative Data | Germany | MATTR customers using MATTR website | | **Jotform** | Jotform is used for form submission on MATTR websites. | Customer Data - Representative Data | United States | MATTR customers using MATTR website | | **Microsoft (Office 365)** | Office365 is a collaboration tool used for communication with customers and may store customer specific documentation. | Customer Data - Ancillary | United States | All MATTR Customers | | **Notion** | Used to store specific information including customer contact information for incident management and other repositories of operational information / designs etc. | Customer Data - Ancillary
Customer Data - Representative Data | United States | All MATTR Customers | | **Slack Technologies, Inc.** | Slack is a collaboration tool used for communication with customers and MATTR product support. Also used internally in the context of Incident Management. | Customer Data
Customer Data - Personal Information | United States | All MATTR Customers | | **Stripe, Inc.** | Stripe supports online payments for MATTR customers. | Customer Data | United States | Customers paying bills by credit card | | **Webflow** | Webflow is a cloud-based development platform used by the MATTR website. | Customer Data - Representative Data | United States | MATTR customers using MATTR website | | **Xero Limited** | Xero is MATTR’s accounting software and is used for invoicing and payments. | Customer Data - Ancillary | New Zealand | All MATTR Customers | For more information regarding data sub-processors (including more detailed information regarding the type of data processed and the storage location) please contact [privacy@mattr.global](mailto:privacy@mattr.global). ## Previous versions [#previous-versions] * [10 June 2025](/docs/resources/terms/data-sub-processors/10-6-25) * [8 Nov 2024](/docs/resources/terms/data-sub-processors/8-11-24) * [28 May 2024](/docs/resources/terms/data-sub-processors/28-5-24) * [25 Mar 2024](/docs/resources/terms/data-sub-processors/25-3-24) * [16 May 2023](/docs/resources/terms/data-sub-processors/16-5-23) # MATTR Data Sub-processors URL: /docs/resources/terms/data-sub-processors/2-2-26 This document is provided for archival purposes only. Last Updated: 2 Feb 2026 [See what's changed](/docs/resources/terms/data-sub-processors/recent-changes) | [Previous versions](#previous-versions) A data sub-processor is a service or capability hosted outside the core MATTR cloud environment that may be used to process Customer Data, such as configuration data, logs, or master data. Alternatively, it may be a capability employed to secure an environment where Customer Data is stored. MATTR may engage the following entities to carry out specific data sub-processing activities: ## MATTR as a Data Processor [#mattr-as-a-data-processor] | Data Sub Processor | Data Processed | Data location | Relevant MATTR Products | | :---------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :------------------------------------ | | **Amazon Web Services, Inc.** | Amazon Web Services (AWS) provides cloud infrastructure services from data centres based in a region agreed with MATTR customers. MATTR also uses other AWS products and services. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | | **Atlassian Pty Ltd** | JIRA service desk is used for logging and managing incidents and service requests (which may contain some Customer data including personal information to support incident investigation). | Australia | MATTR VII
MATTR Pi
MATTR GO | | **DataDog** | Datadog is used for monitoring the operational health of MATTR's platforms. | United States | MATTR VII
MATTR Pi
MATTR GO | | **Doppler** | Doppler is used for configuration management of MATTR GO mobile applications. | United States | MATTR GO | | **GitHub** | GitHub is used to provide customers with access to specific MATTR capabilities such as SDKs. | United States | MATTR Pi | | **MATTR Control Plane** | Listed for completeness, MATTR operates a global control plane as a distributed management system, designed to oversee and manage all MATTR regional deployments worldwide. This control plane can be distributed across Australia, New Zealand, The United Kingdom, Canada, the United States or Germany. The global control plane does not store logs, customer data, or any sensitive information. | Australia
Canada
United Kingdom
USA
Germany | MATTR VII
MATTR Pi
MATTR GO | | **Microsoft (EntraID)** | EntraID is the Identity provider for MATTR employees single sign-on across internal MATTR enterprise systems and cloud infrastructure. | Australia | MATTR VII
MATTR Pi
MATTR GO | | **NPM** | NPM is used to provide customers with access to specific MATTR capabilities such as SDKs. | United States | MATTR Pi | | **Okta, Inc.** | Okta is the Identity provider used to manage access to APIs and user accounts (for access to the MATTR Portal). Deployed in the same (or adjacent) region to the MATTR cloud capability. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | | **Sentry** | Sentry is an error monitoring platform used by MATTR GO Mobile Applications and the MATTR Portal. | United States | MATTR VII
MATTR GO | | **Spark New Zealand Limited** | Spark provides a Security Operations Centre (SOC) and Security Information and Event Management (SIEM) services. | New Zealand | MATTR VII
MATTR Pi
MATTR GO | | **Sumo Logic, Inc.** | Sumo Logic is a cloud-based log management capability used for audit and monitoring of MATTR's capabilities. Deployed in the same (or adjacent) region to the MATTR cloud capability. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | ## MATTR as a Data Controller [#mattr-as-a-data-controller] | Data Sub Processor | Data Processed | Data Category | Data Location | Relevant MATTR Products | | :--------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------- | :------------ | :------------------------------------ | | **Docusign** | Used to electronically sign contractual information with MATTR customers | Customer Data - Ancillary | Australia | MATTR customers using Docusign | | **InnoCraft Limited (Matomo Analytics)** | Matomo is used for website analytics tracking on MATTR websites. | Customer Data - Representative Data | Germany | MATTR customers using MATTR website | | **Jotform** | Jotform is used for form submission on MATTR websites. | Customer Data - Representative Data | United States | MATTR customers using MATTR website | | **Microsoft (Office 365)** | Office365 is a collaboration tool used for communication with customers and may store customer specific documentation. | Customer Data - Ancillary | United States | All MATTR Customers | | **Notion** | Used to store specific information including customer contact information for incident management and other repositories of operational information / designs etc. | Customer Data - Ancillary
Customer Data - Representative Data | United States | All MATTR Customers | | **Slack Technologies, Inc.** | Slack is a collaboration tool used for communication with customers and MATTR product support. Also used internally in the context of Incident Management. | Customer Data
Customer Data - Personal Information | United States | All MATTR Customers | | **Stripe, Inc.** | Stripe supports online payments for MATTR customers. | Customer Data | United States | Customers paying bills by credit card | | **Webflow** | Webflow is a cloud-based development platform used by the MATTR website. | Customer Data - Representative Data | United States | MATTR customers using MATTR website | | **Xero Limited** | Xero is MATTR's accounting software and is used for invoicing and payments. | Customer Data - Ancillary | New Zealand | All MATTR Customers | For more information regarding data sub-processors (including more detailed information regarding the type of data processed and the storage location) please contact [privacy@mattr.global](mailto:privacy@mattr.global). ## Previous versions [#previous-versions] * [17 Oct 2025](/docs/resources/terms/data-sub-processors/17-10-25) * [10 June 2025](/docs/resources/terms/data-sub-processors/10-6-25) * [8 Nov 2024](/docs/resources/terms/data-sub-processors/8-11-24) * [28 May 2024](/docs/resources/terms/data-sub-processors/28-5-24) * [25 Mar 2024](/docs/resources/terms/data-sub-processors/25-3-24) * [16 May 2023](/docs/resources/terms/data-sub-processors/16-5-23) # MATTR Data Sub-processors URL: /docs/resources/terms/data-sub-processors/23-3-26 This document is provided for archival purposes only. Last Updated: 23 March 2026 [See what's changed](/docs/resources/terms/data-sub-processors/recent-changes) | [Previous versions](#previous-versions) A data sub-processor is a service or capability hosted outside the core MATTR cloud environment that may be used to process Customer Data, such as configuration data, logs, or master data. Alternatively, it may be a capability employed to secure an environment where Customer Data is stored. MATTR may engage the following entities to carry out specific data sub-processing activities: ## MATTR as a Data Processor [#mattr-as-a-data-processor] | Data Sub Processor | Data Processed | Data location | Relevant MATTR Products | | :---------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------- | :------------------------------------ | | **Amazon Web Services, Inc.** | Amazon Web Services (AWS) provides cloud infrastructure services from data centres based in a region agreed with MATTR customers. MATTR also uses other AWS products and services. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | | **Atlassian Pty Ltd** | JIRA service desk is used for logging and managing incidents and service requests (which may contain some Customer data including personal information to support incident investigation). | Australia | MATTR VII
MATTR Pi
MATTR GO | | **DataDog** | Datadog is used for monitoring the operational health of MATTR's platforms. | United States | MATTR VII
MATTR Pi
MATTR GO | | **Doppler** | Doppler is used for configuration management of MATTR GO mobile applications. | United States | MATTR GO | | **GitHub** | GitHub is used to provide customers with access to specific MATTR capabilities such as SDKs. | United States | MATTR Pi | | **MATTR Control Plane** | Listed for completeness, MATTR operates a global control plane as a distributed management system, designed to oversee and manage all MATTR regional deployments worldwide. This control plane can be distributed across Australia, New Zealand, The United Kingdom, Canada, the United States or Germany. The global control plane does not store logs, customer data, or any sensitive information. | Australia
Canada
United Kingdom
USA
Germany | MATTR VII
MATTR Pi
MATTR GO | | **Microsoft (EntraID)** | EntraID is the Identity provider for MATTR employees single sign-on across internal MATTR enterprise systems and cloud infrastructure. | Australia | MATTR VII
MATTR Pi
MATTR GO | | **NPM** | NPM is used to provide customers with access to specific MATTR capabilities such as SDKs. | United States | MATTR Pi | | **Okta, Inc.** | Okta is the Identity provider used to manage access to APIs and user accounts (for access to the MATTR Portal). Deployed in the same (or adjacent) region to the MATTR cloud capability. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | | **Red Canary** | Red Canary provides Security Operations Centre (SOC) and Managed Detection & Response (MDR) services. | United States | MATTR VII
MATTR Pi
MATTR GO | | **Sentry** | Sentry is an error monitoring platform used by MATTR GO Mobile Applications and the MATTR Portal. | United States | MATTR VII
MATTR GO | | **Sumo Logic, Inc.** | Sumo Logic is a cloud-based log management capability used for audit and monitoring of MATTR's capabilities. Deployed in the same (or adjacent) region to the MATTR cloud capability. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | ## MATTR as a Data Controller [#mattr-as-a-data-controller] | Data Sub Processor | Data Processed | Data Category | Data Location | Relevant MATTR Products | | :--------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------- | :------------ | :------------------------------------ | | **Docusign** | Used to electronically sign contractual information with MATTR customers | Customer Data - Ancillary | Australia | MATTR customers using Docusign | | **HubSpot** | HubSpot stores and manages information about MATTR's customers for sales and opportunity management, and other operational functions. | Customer Data - Ancillary
Customer Data - Representative Data | Australia | All MATTR customers | | **InnoCraft Limited (Matomo Analytics)** | Matomo is used for website analytics tracking on MATTR websites. | Customer Data - Representative Data | Germany | MATTR customers using MATTR website | | **Jotform** | Jotform is used for form submission on MATTR websites. | Customer Data - Representative Data | United States | MATTR customers using MATTR website | | **Microsoft (Office 365)** | Office365 is a collaboration tool used for communication with customers and may store customer-specific documentation. | Customer Data - Ancillary | United States | All MATTR Customers | | **Notion** | Used to store specific information including customer contact information for incident management and other repositories of operational information / designs etc. | Customer Data - Ancillary
Customer Data - Representative Data | United States | All MATTR Customers | | **Slack Technologies, Inc.** | Slack is a collaboration tool used for communication with customers and MATTR product support. Also used internally in the context of Incident Management. | Customer Data
Customer Data - Personal Information | United States | All MATTR Customers | | **Stripe, Inc.** | Stripe supports online payments for MATTR customers. | Customer Data | United States | Customers paying bills by credit card | | **Webflow** | Webflow is a cloud-based development platform used by the MATTR website. | Customer Data - Representative Data | United States | MATTR customers using MATTR website | | **Xero Limited** | Xero is MATTR's accounting software and is used for invoicing and payments. | Customer Data - Ancillary | New Zealand | All MATTR Customers | For more information regarding data sub-processors (including more detailed information regarding the type of data processed and the storage location) please contact [privacy@mattr.global](mailto:privacy@mattr.global). ## Previous versions [#previous-versions] * [2 Feb 2026](/docs/resources/terms/data-sub-processors/2-2-26) * [17 Oct 2025](/docs/resources/terms/data-sub-processors/17-10-25) * [10 June 2025](/docs/resources/terms/data-sub-processors/10-6-25) * [8 Nov 2024](/docs/resources/terms/data-sub-processors/8-11-24) * [28 May 2024](/docs/resources/terms/data-sub-processors/28-5-24) * [25 Mar 2024](/docs/resources/terms/data-sub-processors/25-3-24) * [16 May 2023](/docs/resources/terms/data-sub-processors/16-5-23) # MATTR Data Sub-processors (archived) URL: /docs/resources/terms/data-sub-processors/25-3-24 This document is provided for archival purposes only. Last Updated: 25 Mar 2024 [See what's changed](/docs/resources/terms/data-sub-processors/recent-changes) | [Previous versions](#previous-versions) MATTR may engage the following entities to carry out specific Personal Data Processing activities: | Entity | Activity | Location | | :----------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | :------------- | | Amazon Web Services, Inc. | Amazon Web Services provides cloud infrastructure services from data centres based in a region agreed with MATTR’s customers. | United States | | Atlassian Pty Ltd | Jira Service Desk is used by MATTR’s customers for logging and tracking of service requests and incidents. | Australia | | InnoCraft Limited (Matomo Analytics) | Matomo may be used for website analytics tracking on MATTR websites. | New Zealand | | MAKE (previously Integromat) | MAKE is used for the provisioning of trial accounts | Czech Republic | | Microsoft | Office365 is a collaboration tool used for communication with customers and may store customer specific documents. | United States | | Okta, Inc. | Okta is an Identity provider used to manage access to MATTR APIs and self-service capabilities. | United States | | Pardot (Salesforce Marketing Cloud) | Pardot is used for marketing automation and onboarding assistance. | United States | | Salesforce, Inc. | Salesforce stores and manages information about MATTR’s customers for operational reasons (e.g. customer contacts) | United States | | Slack Technologies, Inc. | Slack is a collaboration tool used for communication with customers and MATTR product support. | United States | | Spark New Zealand Limited | Spark provides a Security Operations Centre (SOC) and Security Information and Event Management (SIEM) services. | New Zealand | | Stripe, Inc. | Stripe supports online payments for MATTR customers. | United States | | Sumo Logic, Inc. | Sumo Logic is a cloud-based log management capability used for audit and monitoring of MATTR’s capabilities. | United States | | Xero Limited | Xero is MATTR’s accounting software. | New Zealand | For more information regarding data sub-processors (including more detailed information regarding the type of data processed and the storage location) please contact [privacy@mattr.global](mailto:privacyx@mattr.global). ## Previous versions [#previous-versions] * [16 May 2023](/docs/resources/terms/data-sub-processors/16-5-23) # MATTR Data Sub-processors (archived) URL: /docs/resources/terms/data-sub-processors/28-5-24 This document is provided for archival purposes only. Last Updated: 28 May 2024 [See what's changed](/docs/resources/terms/data-sub-processors/recent-changes) | [Previous versions](#previous-versions) MATTR may engage the following entities to carry out specific Personal Data Processing activities: | Entity | Activity | Location | | :----------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | :------------- | | Amazon Web Services, Inc. | Amazon Web Services provides cloud infrastructure services from data centres based in a region agreed with MATTR’s customers. | United States | | Atlassian Pty Ltd | Jira Service Desk is used by MATTR’s customers for logging and tracking of service requests and incidents. | Australia | | InnoCraft Limited (Matomo Analytics) | Matomo may be used for website analytics tracking on MATTR websites. | New Zealand | | Jotform | Jotform is used for form submissions on MATTR websites. | United States | | MAKE (previously Integromat) | MAKE is used for the provisioning of trial accounts | Czech Republic | | Microsoft | Office365 is a collaboration tool used for communication with customers and may store customer specific documents. | United States | | Okta, Inc. | Okta is an Identity provider used to manage access to MATTR APIs and self-service capabilities. | United States | | Pardot (Salesforce Marketing Cloud) | Pardot is used for marketing automation and onboarding assistance. | United States | | Salesforce, Inc. | Salesforce stores and manages information about MATTR’s customers for operational reasons (e.g. customer contacts) | United States | | Slack Technologies, Inc. | Slack is a collaboration tool used for communication with customers and MATTR product support. | United States | | Spark New Zealand Limited | Spark provides a Security Operations Centre (SOC) and Security Information and Event Management (SIEM) services. | New Zealand | | Stripe, Inc. | Stripe supports online payments for MATTR customers. | United States | | Sumo Logic, Inc. | Sumo Logic is a cloud-based log management capability used for audit and monitoring of MATTR’s capabilities. | United States | | Xero Limited | Xero is MATTR’s accounting software. | New Zealand | For more information regarding data sub-processors (including more detailed information regarding the type of data processed and the storage location) please contact [privacy@mattr.global](mailto:privacyx@mattr.global). ## Previous versions [#previous-versions] * [25 Mar 2024](/docs/resources/terms/data-sub-processors/25-3-24) * [16 May 2023](/docs/resources/terms/data-sub-processors/16-5-23) # MATTR Data Sub-processors (archived) URL: /docs/resources/terms/data-sub-processors/8-11-24 This document is provided for archival purposes only. Last Updated: 8 Nov 2024 [See what's changed](/docs/resources/terms/data-sub-processors/recent-changes) | [Previous versions](#previous-versions) A data sub-processor is a service or capability hosted outside the core MATTR cloud environment that may be used to process Customer Data, such as configuration data, logs, or master data. Alternatively, it may be a capability employed to secure an environment where Customer Data is stored. MATTR may engage the following entities to carry out specific data sub-processing activities: | Entity | Activity | Organisation location | | :----------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------- | | Amazon Web Services, Inc. | Amazon Web Services (AWS) provides cloud infrastructure services from data centres based in a region agreed with MATTR customers. MATTR also uses other AWS products and services. | United States | | Atlassian Pty Ltd | JIRA service desk is used for logging and managing incidents and service requests (which may contain come Customer data to support incident investigation). | Australia | | DataDog | Datadog is used for monitoring the operational health of MATTR’s platforms. | United States | | Doppler | Doppler is used for configuration management of MATTR GO Applications. | United States | | InnoCraft Limited (Matomo Analytics) | Matomo is used for website analytics tracking on MATTR websites. | Germany | | Jotform | Jotform is used for form submission on MATTR websites. | United States | | MAKE (previously Integromat) | MAKE is used for the provisioning of trial accounts. | Czech Republic | | Microsoft (Office 365) | Office365 is a collaboration tool used for communication with customers and may store customer specific documentation. | United States | | Microsoft (EntraID) | EntraID is the Identity provider for MATTR employees single sign-on across internal MATTR enterprise systems and cloud infrastructure. | Australia | | Okta, Inc. | Okta is the Identity provider used to manage access to APIs and user accounts for access to the Self Service Portal. Deployed in the same (or adjacent) region to the MATTR cloud capability. | United States | | Pardot (Salesforce Marketing Cloud) | Pardot is used for marketing automation and customer onboarding assistance. | United States | | Salesforce, Inc. | Salesforce.com is used for sales and opportunity management and may store information about MATTR customers, prospective MATTR customers and Trial users. | United States | | Sentry | Sentry is an error monitoring platform used by MATTR GO Mobile Applications. | United States | | Slack Technologies, Inc. | Slack is a collaboration tool used for communication with customers and MATTR product support. | United States | | Spark New Zealand Limited | Spark provides a Security Operations Centre (SOC) and Security Information and Event Management (SIEM) services. | New Zealand | | Stripe, Inc. | Stripe supports online payments for MATTR customers. | United States | | Sumo Logic, Inc. | Sumo Logic is a cloud-based log management capability used for audit and monitoring of MATTR’s capabilities. Deployed in the same (or adjacent) region to the MATTR cloud capability. | United States | | Webflow | Webflow is a cloud-based development platform used by the MATTR website. | United States | | Xero Limited | Xero is MATTR’s accounting software. | New Zealand | | 6Sense | 6Sense is used for website analytics and sales and opportunity management. | United States | For more information regarding data sub-processors (including more detailed information regarding the type of data processed and the storage location) please contact [privacy@mattr.global](mailto:privacyx@mattr.global). ## Previous versions [#previous-versions] * [28 May 2024](/docs/resources/terms/data-sub-processors/28-5-24) * [25 Mar 2024](/docs/resources/terms/data-sub-processors/25-3-24) * [16 May 2023](/docs/resources/terms/data-sub-processors/16-5-23) # MATTR Data Sub-processors URL: /docs/resources/terms/data-sub-processors Last Updated: 12 June 2026 [See what's changed](/docs/resources/terms/data-sub-processors/recent-changes) | [Previous versions](#previous-versions) A data sub-processor is a service or capability hosted outside the core MATTR cloud environment that may be used to process Customer Data, such as configuration data, logs, or master data. Alternatively, it may be a capability employed to secure an environment where Customer Data is stored. MATTR may engage the following entities to carry out specific data sub-processing activities: ## MATTR as a Data Processor [#mattr-as-a-data-processor] | Data Sub Processor | Data Processed | Data location | Relevant MATTR Products | | :------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------ | | **Amazon Web Services, Inc.** | Amazon Web Services (AWS) provides cloud infrastructure services from data centres based in a region agreed with MATTR customers. MATTR also uses other AWS products and services. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | | **Atlassian Pty Ltd** | JIRA service desk is used for logging and managing incidents and service requests (which may contain some Customer data including personal information to support incident investigation). | Australia | MATTR VII
MATTR Pi
MATTR GO | | **Checkly Inc. (and Checkly GmbH)** | Checkly is used for monitoring the operational health of MATTR’s platforms. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | | **Doppler Technologies, Inc.** | Doppler is used for configuration management of MATTR GO mobile applications. | United States | MATTR GO | | **GitHub, Inc.** | GitHub is used to provide customers with access to specific MATTR capabilities such as SDKs. | United States | MATTR Pi | | **Microsoft Corporation (EntraID)** | EntraID is the Identity provider for MATTR employees single sign-on across internal MATTR enterprise systems and cloud infrastructure. | Australia | MATTR VII
MATTR Pi
MATTR GO | | **npm, Inc.** | npm is used to provide customers with access to specific MATTR capabilities such as SDKs. | United States | MATTR Pi | | **Okta, Inc.** | Okta is used as an Authorisation Server for MATTR VII APIs.
Okta is also used as an IDP to support the MATTR Global Control Hub, including the Management APIs (M2M Clients) and MATTR Portal (Users), which are used to oversee and manage access to cloud environments. | The Okta Authorisation Server is Cloud region aligned **where possible**,

The Okta IDP (as part of MATTR Global Control Hub) is based in Australia. | MATTR VII
MATTR Pi
MATTR GO | | **Red Canary, Inc.** | Red Canary provides Security Operations Centre (SOC) and Managed Detection & Response (MDR) services. | United States | MATTR VII
MATTR Pi
MATTR GO | | **Sentry (Functional Software, Inc.)** | Sentry is an error monitoring platform used by MATTR GO Mobile Applications and the MATTR Portal. | United States | MATTR VII
MATTR GO | | **Sumo Logic, Inc.** | Sumo Logic is a cloud-based log management capability used for audit and monitoring of MATTR’s capabilities. Deployed in the same (or adjacent) region to the MATTR cloud capability. | Cloud region aligned **where possible** | MATTR VII
MATTR Pi
MATTR GO | ## MATTR as a Data Controller [#mattr-as-a-data-controller] | Data Sub Processor | Data Processed | Data Category | Data Location | Relevant MATTR Products | | :--------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------- | :------------ | :------------------------------------ | | **Docusign, Inc.** | Used to electronically sign contractual information with MATTR customers | Customer Data - Ancillary | Australia | MATTR customers using Docusign | | **HubSpot, Inc.** | HubSpot stores and manages information about MATTR's customers for sales and opportunity management, and other operational functions. | Customer Data - Ancillary
Customer Data - Representative Data | Australia | All MATTR customers | | **InnoCraft Limited (Matomo Analytics)** | Matomo is used for website analytics tracking on MATTR websites. | Customer Data - Representative Data | Germany | MATTR customers using MATTR website | | **Microsoft Corporation (Office 365)** | Office 365 is a collaboration tool used for communication with customers and may store customer-specific documentation. | Customer Data - Ancillary | United States | All MATTR Customers | | **Notion Labs, Inc.** | Used to store specific information including customer contact information for incident management and other repositories of operational information / designs etc. | Customer Data - Ancillary
Customer Data - Representative Data | United States | All MATTR Customers | | **Slack Technologies, LLC** | Slack is a collaboration tool used for communication with customers and MATTR product support. Also used internally in the context of Incident Management. | Customer Data
Customer Data - Personal Information | United States | All MATTR Customers | | **Stripe, LLC** | Stripe supports online payments for MATTR customers. | Customer Data | United States | Customers paying bills by credit card | | **Webflow, Inc.** | Webflow is a cloud-based development platform used by the MATTR website. | Customer Data - Representative Data | United States | MATTR customers using MATTR website | | **Xero Limited** | Xero is MATTR’s accounting software and is used for invoicing and payments. | Customer Data - Ancillary | New Zealand | All MATTR Customers | For more information regarding data sub-processors (including more detailed information regarding the type of data processed and the storage location) please contact [privacy@mattr.global](mailto:privacy@mattr.global). ## Previous versions [#previous-versions] * [15 Apr 2026](/docs/resources/terms/data-sub-processors/15-4-26) * [23 Mar 2026](/docs/resources/terms/data-sub-processors/23-3-26) * [2 Feb 2026](/docs/resources/terms/data-sub-processors/2-2-26) * [17 Oct 2025](/docs/resources/terms/data-sub-processors/17-10-25) * [10 June 2025](/docs/resources/terms/data-sub-processors/10-6-25) * [8 Nov 2024](/docs/resources/terms/data-sub-processors/8-11-24) * [28 May 2024](/docs/resources/terms/data-sub-processors/28-5-24) * [25 Mar 2024](/docs/resources/terms/data-sub-processors/25-3-24) * [16 May 2023](/docs/resources/terms/data-sub-processors/16-5-23) # MATTR Data Sub-processors - What's Changed URL: /docs/resources/terms/data-sub-processors/recent-changes ## Changes posted June 12, 2026 [#changes-posted-june-12-2026] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on June 12, 2026: * Removal of Datadog * Addition of Checkly Inc. (and Checkly GmbH) * Update of sub-processor names to reflect their registered entity names ## Changes posted April 15, 2026 [#changes-posted-april-15-2026] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on April 15, 2026: * Removal of the MATTR Control Plane entry * Removal of the Jotform entry * Updated Okta, Inc. entry to reflect its dual role as an Authorisation Server for MATTR VII APIs and as an IDP for the MATTR Global Control Hub ## Changes posted March 23, 2026 [#changes-posted-march-23-2026] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on March 23, 2026: * Removal of Spark New Zealand Limited * Addition of Red Canary * Addition of HubSpot ## Changes posted Feb 2, 2026 [#changes-posted-feb-2-2026] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on February 2, 2026: * Updated the Sentry Data Sub-processor entry to reflect its use in the MATTR Portal. ## Changes posted Oct 17, 2025 [#changes-posted-oct-17-2025] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on October 17, 2025: * Split list based on data usage pattern (MATTR as a Data Controller vs Data Processor) * Removal of the following Data Sub-processors: * 6Sense * Salesforce * Pardot ## Changes posted June 10, 2025 [#changes-posted-june-10-2025] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on June 10, 2025: Addition of the following Data Sub-processors: * GitHub ## Changes posted Nov 8, 2024 [#changes-posted-nov-8-2024] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on Nov 8, 2024: Addition of the following Data Sub-processors: * DataDog * Doppler * Microsoft (EntraID) * Sentry * Webflow * 6Sense ## Changes posted May 28, 2024 [#changes-posted-may-28-2024] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on May 28, 2024: * Addition of Jotform ## Changes posted Mar 25, 2024 [#changes-posted-mar-25-2024] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on March 25, 2024. * Removal of Mezmo Inc. * Removal of Netlify Inc. ## Changes posted May 16, 2023 [#changes-posted-may-16-2023] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on May 16, 2023. * Addition of SumoLogic Inc, MAKE and Okta Inc. * Removal of Google. ## Changes posted December 9, 2021 [#changes-posted-december-9-2021] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on December 9, 2021. * Addition of Matomo Analytics, Pardot and Google. ## Changes posted March 25, 2021 [#changes-posted-march-25-2021] MATTR updated the [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) on March 25, 2021. The [MATTR Data Sub-processors](/docs/resources/terms/data-sub-processors) was published. Your use of our Services after the effective date of this update confirms that you have read and understood the updated MATTR Data Sub-processors. # Mobile Wallet Privacy Policy (archived) URL: /docs/resources/terms/go-applications/15-9-20 This document is provided for archival purposes only. Last Updated: 15 September 2020 Your privacy is important to us, and we take it seriously. This privacy policy (**Privacy Policy**) explains how we collect, use and disclose personal information in relation to your use of the MATTR Mobile Wallet (“**Mobile Wallet**“) and areas of connection with the MATTR Platform (“**Platform**“). As a New Zealand based company, we comply with the New Zealand Privacy Act 1993 (**the Act**) when dealing with personal information. This Privacy Policy does not limit or exclude any of your rights under the Act.\ You can find more information about the Act and your rights at [www.privacy.org.nz](https://www.privacy.org.nz). When we say “**our**,” “**we**,” or “**us**,” we are talking about MATTR Limited (**MATTR**). When we say “**you**” or “**your**” we are referring to you, the end-user of the Mobile Wallet. # MATTR Wallet Terms of Use (archived) URL: /docs/resources/terms/go-applications/23-9-22 This document is provided for archival purposes only. Last Updated: 23 September 2022 [See what's changed](/docs/resources/terms/go-applications/recent-changes) Please note that the English language version of the Terms of Use are binding and the most up-to-date. If there is any inconsistency between the English language version of the Terms of Use and any versions in other languages that we provide to you, the English language version will prevail to the extent of the inconsistency. 1. **OVERVIEW** These Terms of Use are between MATTR Limited (**MATTR, we**, **us**, or **our**) and you or the entity you represent (**you** or **your**). These Terms of Use govern your use of the MATTR Wallet (which includes downloading, installing, accessing and using) (**use**) on any devices with which the MATTR Wallet is compatible (**Mobile Device**). Any reference in these Terms of Use to the MATTR Wallet must be read as including a reference to data, information and content made available through the MATTR Wallet. By using the MATTR Wallet, you agree to these Terms of Use. If you do not agree, you must uninstall and not use the MATTR Wallet. 2. **ELIGIBILITY** You are eligible to use the MATTR Wallet (an **Eligible User**) if you are either: (a) a user who wishes to test and evaluate the MATTR Wallet; or (b) a user who has been invited to participate in any MATTR-approved pilot listed on our website at [https://learn.mattr.global/docs/resources/terms/go-applications/pilots](https://learn.mattr.global/docs/resources/terms/go-applications/pilots) at the time of use. By using the MATTR Wallet, you represent and warrant that you are an Eligible User. If you are not (or no longer) an Eligible User, you must uninstall and not use the MATTR Wallet. 3. **SCOPE OF LICENCE** Subject to clauses 2 and 4: (a) we grant you the revocable, non-exclusive, non-transferable right to use the MATTR Wallet on any Mobile Device that you own and control. Specifically: (i) if you are a participant in a MATTR-approved pilot, you can use the MATTR Wallet for the sole purpose of participating in the pilot, including to provide feedback on the usability of the MATTR Wallet in the ecosystem to which the pilot relates; and (ii) otherwise, you can use the MATTR Wallet for the sole purpose of evaluating the capabilities of the MATTR Wallet and you will not use the MATTR Wallet for commercial purposes unless otherwise agreed by us in writing. (b) you understand and agree that: (i) the MATTR Wallet is not offered as a commercially supported product and is intended to be used for testing and evaluation purposes only; (ii) accordingly, as a result of user feedback and/or customer requirements (including any change to the pilot) and/or to protect MATTR’s legitimate business interests, MATTR may, at any time and without notice, upgrade, update, replace or discontinue the MATTR Wallet, and/or change the terms on which the MATTR Wallet is offered (including as to any charges that may apply); and (iii) if you do not agree with any such changes, your sole remedy is to terminate these Terms of Use in accordance with clause 10. (c) You must not distribute the MATTR Wallet or in any way make it available to any third party, including over a network where it could be used by multiple devices or end users at the same time. Unless otherwise specified by MATTR, these Terms of Use govern all versions of the MATTR Wallet, including any upgrades or updates provided or otherwise made available by MATTR. (d) You must not reverse-engineer, decompile, disassemble, attempt to derive the source code of, modify, copy or create derivative works of the MATTR Wallet, any updates, or any part of the MATTR Wallet (except to the extent that any such restriction is prohibited by applicable law). 4. **OPEN SOURCE SOFTWARE** (a) The MATTR Wallet may include open source software that is subject to one or more open source licence (**Open Source Software**). Any such Open Source Software is licensed under its applicable licence terms, and is not subject to the terms and conditions of these Terms of Use unless otherwise specified. For a full list of Open Source Software used in the MATTR Wallet, including third-party notices containing credits or attributions where required for the use or redistribution of such Open Source Software, please visit [https://learn.mattr.global/docs/holding/go-hold/libraries](https://learn.mattr.global/docs/holding/go-hold/libraries) (b) Even where Open Source Software is governed by other licence terms, the limitations of liability set out in clause 8 will continue to apply as between you and MATTR, except to the extent that any such limitations are prohibited by applicable law. 5. **USE OF DATA** MATTR may collect, use, disclose and otherwise deal with technical data and related information, including technical information about your Mobile Device, system and application software, and peripherals, that is gathered to facilitate the provision of software updates, product support, and other services related to the MATTR Wallet. MATTR may use this information, as long as it is in a form that does not personally identify you, to improve our products or to provide services or technologies to you. If we collect, use, disclose or otherwise deal with your personal information, we will do so in accordance with our Privacy Policy, and our Data Processing Terms if applicable, which are available on our website at [https://learn.mattr.global/docs/resources/terms/privacy-policy](https://learn.mattr.global/docs/resources/terms/privacy-policy) 6. **INTELLECTUAL PROPERTY RIGHTS** All intellectual property rights in and to the MATTR Wallet, including copyright, are owned by MATTR or its licensors or assignees. Other than as expressly provided in these Terms of Use, nothing in these Terms of Use operates to transfer or assign ownership of intellectual property rights, or confers on you any right, title or interest in or to any of MATTR’s intellectual property rights or to any third party intellectual property rights. 7. **NO WARRANTY** Use of the MATTR Wallet is solely at your risk. The MATTR Wallet is provided strictly on an “as is” and “as available” basis with no express or implied warranty or representation of any kind. MATTR does not represent or warrant: (a) the accuracy, merchantability, non-infringement, or fitness of the MATTR Wallet for any particular purpose; (b) the accuracy or completeness of any data, information or content (including any text, graphics, links or other items) contained in or made available through the MATTR Wallet or through any platforms, including third party platforms, we use to communicate with you in relation to the MATTR Wallet; (c) that the MATTR Wallet will be updated or supported by MATTR at any particular frequency or any particular period of time, or that the MATTR Wallet will support the creation and maintenance of backups; (d) that use of the MATTR Wallet will be uninterrupted or error-free; or (e) that the MATTR Wallet will be free of viruses, worms, denial of service attacks, or other harmful components or codes. In some places, like New Zealand and Australia, there may be non-excludable warranties, guarantees or other rights provided by law. They still apply - these Terms of Use do not exclude, restrict or modify them. Clause 8 sets out our liability in relation to such non-excludable consumer guarantees. 8. **LIMITATION OF LIABILITY** To the maximum extent permitted by law, MATTR will not be liable for any direct or indirect liability, loss, damage, cost or expense (including loss of profits, loss of revenue, business interruption, loss of savings, loss of information or data, or any indirect, special, incidental or consequential loss of any kind) arising out of or in connection with your access and use of (or inability to access and use) the MATTR Wallet, whether in contract, tort (including negligence), equity, breach of statutory duty or otherwise, even if MATTR has been advised of the possibility of such damages. If required under the terms of the store from which you download and install the MATTR Wallet onto your Mobile Device, MATTR will provide you with a refund of any fees paid for the MATTR Wallet. Where applicable, any such refund is MATTR’s sole and exclusive liability to you for any liability, loss, damages, costs and expenses (other than for any liability, loss, damage, costs and expenses that cannot be limited at law). The refund of fees in any other circumstances is at MATTR’s sole discretion. To the extent that the preceding limitations of liability are held to be invalid in whole or in part in relation to any liability, loss, damages, costs and expenses, then unless the following paragraph applies, MATTR’s total liability to you for any such liability, loss, damages, costs and expense (other than to the extent they cannot be limited at law) will not exceed NZD\$50.00. Nothing in these Terms of Use affects any non-excludable warranties, guarantees or other rights provided by law as described in clause 7. Our liability for breach of such non-excludable consumer guarantee is limited, at our option, to re-performing the relevant service (unless the non-excludable consumer guarantee says otherwise). 9. **THIRD PARTY MATERIALS AND TERMS** This clause 9 is not to be read as limiting any other provision of these Terms of Use. The MATTR Wallet may display, or make available certain third party data, information or content (**Third Party Materials**). You use the Third Party Materials at your own risk. MATTR is not responsible for examining or evaluating the content or accuracy of any Third Party Materials, and will not be liable in relation to any Third Party Materials. You must not use the Third Party Materials in any manner that is inconsistent with these Terms of Use or that infringes the intellectual property rights of MATTR or any third party. You are solely responsible for complying with the terms of any applicable third party agreement or licence terms that apply in connection with such Third Party Materials. 10. **TERMINATION** These Terms of Use are effective until terminated by you or MATTR. Your rights under these Terms of Use will terminate automatically if you fail to comply with any of its terms. If you are a participant in a MATTR-approved pilot, your rights under these Terms of Use will terminate automatically when the pilot ends, unless otherwise agreed in writing. If your rights are terminated, you must immediately uninstall and cease to use the MATTR Wallet (including all copies of the MATTR Wallet). You may only terminate Terms of Use by uninstalling and ceasing to use the MATTR Wallet (including all copies of the MATTR Wallet). Termination of these Terms of Use is without prejudice to the rights and obligations accrued up to and including the date of termination and either you or we may take action against the other under these Terms of Use in respect of a breach of Terms of Use arising before the effective date of termination. 11. **GOVERNING LAW** These Terms of Use and any dispute or claim arising out of or in connection with it will be governed by and construed in accordance with the laws of New Zealand and the parties submit to the non-exclusive jurisdiction of the courts of New Zealand to deal with any dispute, litigation or other matter relating to these Terms of Use or the MATTR Wallet. You must not object to the transfer of any proceedings to New Zealand courts on any basis, including inconvenience. The United Nations Convention on Contracts for the International Sale of Goods does not apply to this Agreement. 12. **GENERAL** If, at any time, any provision of these Terms of Use is or becomes illegal, invalid or unenforceable to any extent, then that provision will be read down so that it becomes legal, valid or enforceable, or if that is not possible, then that provision will be deleted. The other terms of these Terms of Use will continue to apply with full force and effect. Any reference in these Terms of Use to “including” and similar expressions are not used as, nor are they intended to be, interpreted as words of limitation. 13. **APPLE REQUIREMENTS** Where you use the MATTR Wallet on an Apple Mobile Device, you acknowledge that: (a) these Terms of Use are between MATTR and you, and not Apple; (b) Apple has no responsibility or liability in respect of any matter relating to the MATTR Wallet, including your use or possession of the MATTR Wallet or the provision of maintenance or support services relating to the MATTR Wallet; (c) MATTR, and not Apple, is responsible for addressing any claims relating to the MATTR Wallet or your possession or use of the MATTR Wallet, including: (i) product liability claims; (ii) any claim that the MATTR Wallet fails to conform to any applicable legal or regulatory requirement; and (iii) claims arising under consumer protection, privacy or similar legislation; (d) if the MATTR Wallet fails to conform to any applicable warranty, you may notify Apple, and Apple’s sole liability will be to refund to you any purchase price for the MATTR Wallet. Apple will have no other warranty obligation whatsoever with respect to the MATTR Wallet, and any other claims, losses, liabilities, damages, costs or expenses attributable to any failure to conform to any warranty not expressly excluded under these Terms of Use will be MATTR’s sole responsibility; and (e) in the event of any third party claim that the MATTR Wallet, or your possession and use of the MATTR Wallet, infringes that third party’s intellectual property rights, MATTR (and not Apple) will be solely responsible for the investigation, defence, settlement and discharge of any such intellectual property infringement claim. MATTR and you agree that Apple, and any Apple subsidiary, are third party beneficiaries of these Terms of Use and that Apple has the right to enforce the Terms of Use against you as a third party beneficiary. Otherwise, these Terms of Use do not create any third-party beneficiary rights in any individual or entity that is not a party to these Terms of Use. You represent and warrant that you are not located in a country that is subject to a US Government embargo, or that has been designated by the US Government as a terrorist supporting country, and you are not listed on any US Government list of prohibited or restricted parties. 14. **CONTACT** If you have any questions, complaints or claims relating to the MATTR Wallet, please contact us at [privacy@mattr.global](mailto:privacy@mattr.global) or Level 1, Union Fish Building, Britomart Place, 116-118 Quay Street, Auckland Central, 1010, New Zealand. ## Previous versions [#previous-versions] * [15 Sep 2020](/docs/resources/terms/go-applications/15-9-20) # MATTR GO Applications accessibility statement URL: /docs/resources/terms/go-applications/accessibility-statement Last Updated: 4 Nov 2024 MATTR is committed to ensuring our products and platforms are accessible to people with disabilities. We believe that applications must be inclusive and all MATTR GO Applications are designed to facilitate an accessible user experience. We draw upon industry standards and accredited accessibility partners to apply best practices. ## Our accessibility goals [#our-accessibility-goals] Our aim is to ensure that the wallet is as accessible as possible. We use the industry standard [Web Content Accessibility Guidelines](https://www.w3.org/TR/WCAG21/) (WCAG 2.1), developed by the W3C Web Accessibility Initiative. Our goal is to make MATTR GO Applications experience logical, simple to read and understand and, hopefully, easier for users to achieve the outcomes they need. ## Our efforts [#our-efforts] We have dedicated time and resources to make the MATTR GO Applications experiences functional, engaging and interactive. These are key areas we’ve addressed to assist in accessibility: * Focus and emphasis on assisted navigation * Navigation behaviours * Signposting and labelling * Colour and contrast * Touch zones * Text size MATTR GO Applications work on both Android and iOS operating systems. Due to the native influence, there are small, cosmetic differences on each system, however, the fundamental adherence to accessibility standards has been implemented across each platform. ## Conformance status [#conformance-status] We are continually improving the user experience for everyone, and will always aim to apply relevant accessibility standards. MATTR GO Applications have undergone a number of checks, utilising industry standard and best practice tooling such as: * Screen readers * Screen magnifiers * Keyboard only checks * Colour contrast checks * and a variety of automated and manual checks MATTR GO Applications are partially conformant with WCAG 2.1 Level AA. Partially conformant means that some parts of the content do not fully conform to the accessibility standard. ## Give us your feedback [#give-us-your-feedback] We welcome your feedback on the accessibility of any MATTR GO Applications. Please let us know if you encounter accessibility barriers on any MATTR GO Application by contacting us at [privacy@mattr.global](mailto:privacy@mattr.global) # Approved Pilots for MATTR GO Applications URL: /docs/resources/terms/go-applications/pilots This page sets out the MATTR-approved pilots for MATTR GO Applications referred to in clause 2(b) of the [MATTR GO Applications Terms of Use](/docs/resources/terms/go-applications/terms-of-use). You are eligible to download, install and use MATTR GO Applications if you have been invited to participate in any of the following pilots: * MyCreds™ Ontario Virtual Skills Passport pilot project for micro-credentials # MATTR GO Applications Terms of Use - What's Changed URL: /docs/resources/terms/go-applications/recent-changes MATTR published the MATTR Developer Tool End User Licence Agreement (EULA) on March 25, 2021. ## Changes posted November 5, 2024 [#changes-posted-november-5-2024] We've renamed the MATTR Wallet Terms of Use into MATTR GO Applications Terms of Use on November 5, 2024. ## Changes posted September 30, 2022 [#changes-posted-september-30-2022] MATTR updated the EULA / Terms of Use on September 30, 2022. We’ve renamed the EULA [MATTR Wallet Terms of Use](/docs/resources/terms/go-applications/terms-of-use) and updated some terms to reflect the changes below: 1. With the Terms of Use being available in multiple languages, we’ve clarified that the English language version of the Terms of Use is binding and the most up-to-date. 2. As the MATTR Wallet is intended to be used for testing and evaluation purposes only, we’ve outlined the 2 categories of users who are eligible to download, install and use the app; that is, individuals who wish to test and evaluate the MATTR Wallet, and participants of approved pilots listed on our site. 3. We’ve noted that we can change the MATTR Wallet in certain circumstances, such as in response to user feedback or to protect our business interests. These changes may include replacing or discontinuing the MATTR Wallet and/or the terms on which it is offered (including any applicable charges). By continuing to use the MATTR Wallet after the effective date of these changes, you agree to be bound by the modified terms. # MATTR GO Applications Terms of Use URL: /docs/resources/terms/go-applications/terms-of-use Last Updated: 5 November 2024 [See what's changed](/docs/resources/terms/go-applications/recent-changes) Please note that the English language version of the Terms of Use are binding and the most up-to-date. If there is any inconsistency between the English language version of the Terms of Use and any versions in other languages that we provide to you, the English language version will prevail to the extent of the inconsistency. 1. **OVERVIEW** These Terms of Use (**Terms**) are between MATTR Limited (**MATTR, we**, **us**, or **our**) and you or the entity you represent (**you** or **your**). Any download, installation, run, access or other use (**use**) of this application and/or any application which states it is subject to these Terms (**App**) on any devices with which the App is compatible (**Device**) is subject to these Terms. By using the App and/or accepting these Terms, you are agreeing to these Terms. If you do not agree, you must uninstall and not use the App. Any reference in these Terms to the App must be read as including a reference to data, information and content made available through the App. 2. **ELIGIBILITY** You are eligible to use the App (an **Eligible User**) if you are either: (a) a user who wishes to test and evaluate the App; or (b) a user who has been invited to participate in any MATTR-approved pilot listed on our [website](https://learn.mattr.global/docs/resources/terms/go-applications/pilots) at the time of use. By using the App, you represent and warrant that you are an Eligible User. If you are not (or no longer) an Eligible User, you must uninstall and not use the App. 3. **SCOPE OF LICENCE** Subject to clauses 2, 4 and 5: (a) we grant you the revocable, non-exclusive, non-transferable right to use the App on any Device that you own and control. Specifically: (i) if you are a participant in a MATTR-approved pilot, you can use the App for the sole purpose of participating in the pilot within the published limitations of that pilot, including to provide feedback on the usability of the App in the ecosystem to which the pilot relates; and (ii) otherwise, (A) you can use the App for a non-commercial, non-production evaluation of the capabilities of the App with the types of credentials, issuers and/or verifies that we notify you from time to time are authorised for use with the App and; and (B) you will not use the App for commercial purposes unless otherwise agreed by us in writing. (b) You must not distribute the App or in any way make it available to any third party, including over a network where it could be used by multiple devices or end users at the same time. Unless otherwise specified by MATTR, these Terms govern all versions of the App, including any upgrades or updates provided or otherwise made available by MATTR. (c) You must not reverse-engineer, decompile, disassemble, attempt to derive the source code of, modify, copy or create derivative works of the App, any updates, or any part of the App (except to the extent that any such restriction is prohibited by applicable law). 4. **TEST PURPOSES ONLY** You understand and agree that: (a) the App is not offered as a commercially supported product and is intended to be used for testing and evaluation purposes only; (b) accordingly, the App is provided as-is, with no warranties at all (express or implied). We use reasonable endeavours to avoid it causing material harm to you. Despite this, the App may: (i) include bugs, errors and security risks; (ii) perform poorly, produce incorrect results, or cease to function; (iii) cause your Device, other software or processes on that Device, or other software or processes with which they interact, to crash or malfunction; (iv) improperly expose, permanently delete or permanently corrupt, information or data of yours. (c) to avoid or mitigate against adverse consequences arising from these risks, you must: (i) limit use of the App to testing purposes only; (ii) take reasonable precautions in respect of your Device and other software and data on that Device and not use the App on any Device that is critical for your other business purposes; and (iii) only enter test or synthetic data into MATTR Services and Product Capabilities under this Order Form and will ensure that no data you enter is, or is likely to constitute, personal information or confidential information. (d) as a result of user feedback and/or customer requirements (including any change to the pilot) and/or to protect MATTR’s legitimate business interests, MATTR may, at any time and without notice, upgrade, update, replace or discontinue the App, and/or change the terms on which the App is offered (including as to any charges that may apply); and if you do not agree with any such changes, your sole remedy is to terminate these Terms in accordance with clause 11. 5. **OPEN SOURCE SOFTWARE** (a) The App may include open source software that is subject to one or more open source licence (**Open Source Software**). Any such Open Source Software is licensed under its applicable licence terms, and is not subject to the terms and conditions of these Terms unless otherwise specified. For a full list of Open Source Software used in the App, including third-party notices containing credits or attributions where required for the use or redistribution of such Open Source Software, please visit [https://learn.mattr.global/docs/holding/go-hold/libraries](https://learn.mattr.global/docs/holding/go-hold/libraries) for wallet or hold Apps and [https://learn.mattr.global/docs/verification/go-verify/libraries](https://learn.mattr.global/docs/verification/go-verify/libraries) for verifier Apps. (b) Even where Open Source Software is governed by other licence terms, the limitations of liability set out in clause 9 will continue to apply as between you and MATTR, except to the extent that any such limitations are prohibited by applicable law. 6. **USE OF DATA** MATTR may collect, use, disclose and otherwise deal with technical data and related information, including technical information about your Device, system and application software, and peripherals, that is gathered to facilitate the provision of software updates, product support, and other services related to the App. MATTR may use this information to improve our products or to provide services or technologies to you. If we collect, use, disclose or otherwise deal with your personal information, we will do so in accordance with our Privacy Policy, and our Data Processing Terms if applicable, which are available on our website at [https://learn.mattr.global/docs/resources/terms/privacy-policy](https://learn.mattr.global/docs/resources/terms/privacy-policy). 7. **INTELLECTUAL PROPERTY RIGHTS** All intellectual property rights in and to the App, including copyright, are owned by MATTR or its licensors or assignees. Other than as expressly provided in these Terms, nothing in these Terms operates to transfer or assign ownership of intellectual property rights, or confers on you any right, title or interest in or to any of MATTR’s intellectual property rights or to any third party intellectual property rights. 8. **NO WARRANTY** Use of the App is solely at your risk. The App is provided strictly on an “as is” and “as available” basis with no express or implied warranty or representation of any kind. MATTR does not represent or warrant: (a) the accuracy, merchantability, non-infringement, or fitness of the App for any particular purpose; (b) the accuracy or completeness of any data, information or content (including any text, graphics, links or other items) contained in or made available through the App or through any platforms, including third party platforms, we use to communicate with you in relation to the App; (c) that the App will be updated or supported by MATTR at any particular frequency or any particular period of time, or that the App will support the creation and maintenance of backups; (d) that use of the App will be uninterrupted or error-free; or (e) that the App will be free of viruses, worms, denial of service attacks, or other harmful components or codes. In some places, like New Zealand and Australia, there may be non-excludable warranties, guarantees or other rights provided by law. They still apply – these Terms do not exclude, restrict or modify them. Clause 9 sets out our liability in relation to such non-excludable consumer guarantees. 9. **LIMITATION OF LIABILITY** To the maximum extent permitted by law, MATTR will not be liable for any direct or indirect liability, loss, damage, cost or expense (including loss of profits, loss of revenue, business interruption, loss of savings, loss of information or data, or any indirect, special, incidental or consequential loss of any kind) arising out of or in connection with your access and use of (or inability to access and use) the App, whether in contract, tort (including negligence), equity, breach of statutory duty or otherwise, even if MATTR has been advised of the possibility of such damages. If required under the terms of the store from which you download and install the App onto your Device, MATTR will provide you with a refund of any fees paid for the App. Where applicable, any such refund is MATTR’s sole and exclusive liability to you for any liability, loss, damages, costs and expenses (other than for any liability, loss, damage, costs and expenses that cannot be limited at law). The refund of fees in any other circumstances is at MATTR’s sole discretion. To the extent that the preceding limitations of liability are held to be invalid in whole or in part in relation to any liability, loss, damages, costs and expenses, then unless the following paragraph applies, MATTR’s total liability to you for any such liability, loss, damages, costs and expense (other than to the extent they cannot be limited at law) will not exceed NZD\$50.00. Nothing in these Terms affects any non-excludable warranties, guarantees or other rights provided by law as described in clause 8. Our liability for breach of such non-excludable consumer guarantee is limited, at our option, to re-performing the relevant service (unless the non-excludable consumer guarantee says otherwise). 10. **THIRD PARTY MATERIALS AND TERMS** This clause 10 is not to be read as limiting any other provision of these Terms. The App may display, or make available certain third party data, information or content (**Third Party Materials**). You use the Third Party Materials at your own risk. MATTR is not responsible for examining or evaluating the content or accuracy of any Third Party Materials, and will not be liable in relation to any Third Party Materials. You must not use the Third Party Materials in any manner that is inconsistent with these Terms or that infringes the intellectual property rights of MATTR or any third party. You are solely responsible for complying with the terms of any applicable third party agreement or licence terms that apply in connection with such Third Party Materials. 11. **TERMINATION** These Terms are effective until terminated by you or MATTR. Your rights under these Terms will terminate automatically if you fail to comply with any of its terms. If you are a participant in a MATTR-approved pilot, your rights under these Terms will terminate automatically when the pilot ends, unless otherwise agreed in writing. If your rights are terminated, you must immediately uninstall and cease to use the App (including all copies of the App). You may only terminate these Terms by uninstalling and ceasing to use the App (including all copies of the App). Termination of these Terms is without prejudice to the rights and obligations accrued up to and including the date of termination and either you or we may take action against the other under these Terms in respect of a breach of Terms arising before the effective date of termination. 12. **GOVERNING LAW** These Terms and any dispute or claim arising out of or in connection with it will be governed by and construed in accordance with the laws of New Zealand and the parties submit to the non-exclusive jurisdiction of the courts of New Zealand to deal with any dispute, litigation or other matter relating to these Terms or the App. You must not object to the transfer of any proceedings to New Zealand courts on any basis, including inconvenience. The United Nations Convention on Contracts for the International Sale of Goods does not apply to this Agreement. 13. **GENERAL** If, at any time, any provision of these Terms is or becomes illegal, invalid or unenforceable to any extent, then that provision will be read down so that it becomes legal, valid or enforceable, or if that is not possible, then that provision will be deleted. The other terms of these Terms of Use will continue to apply with full force and effect. Any reference in these Terms to “including” and similar expressions are not used as, nor are they intended to be, interpreted as words of limitation. 14. **CHANGES TO THESE TERMS** We may amend these Terms at any time, by giving you notice of an amended version. By continuing to use the App after such notice, you agree to the amended Terms. If you do not agree to the amended terms, you must immediately cease your use of the App. 15. **APPLE REQUIREMENTS** Where you use the App on an Apple Device, you acknowledge that: (a) these Terms are between MATTR and you, and not Apple; (b) Apple has no responsibility or liability in respect of any matter relating to the App, including your use or possession of the App or the provision of maintenance or support services relating to the App; (c) MATTR, and not Apple, is responsible for addressing any claims relating to the App or your possession or use of the App, including: (i) product liability claims; (ii) any claim that the App fails to conform to any applicable legal or regulatory requirement; and (iii) claims arising under consumer protection, privacy or similar legislation; (d) if the App fails to conform to any applicable warranty, you may notify Apple, and Apple’s sole liability will be to refund to you any purchase price for the App. Apple will have no other warranty obligation whatsoever with respect to the App, and any other claims, losses, liabilities, damages, costs or expenses attributable to any failure to conform to any warranty not expressly excluded under these Terms will be MATTR’s sole responsibility; and (e) in the event of any third party claim that the App, or your possession and use of the App, infringes that third party’s intellectual property rights, MATTR (and not Apple) will be solely responsible for the investigation, defence, settlement and discharge of any such intellectual property infringement claim. MATTR and you agree that Apple, and any Apple subsidiary, are third party beneficiaries of these Terms and that Apple has the right to enforce the Terms against you as a third party beneficiary. Otherwise, these Terms do not create any third-party beneficiary rights in any individual or entity that is not a party to these Terms. You represent and warrant that you are not located in a country that is subject to a US Government embargo, or that has been designated by the US Government as a terrorist supporting country, and you are not listed on any US Government list of prohibited or restricted parties. 16. **CONTACT** If you have any questions, complaints or claims relating to the App, please contact us at [privacy@mattr.global](mailto:privacy@mattr.global). ## Previous versions [#previous-versions] * [23 Sep 2022](/docs/resources/terms/go-applications/23-9-22) * [15 Sep 2020](/docs/resources/terms/go-applications/15-9-20) # MATTR Privacy Policy (archived) URL: /docs/resources/terms/privacy-policy/10-11-22 This document is provided for archival purposes only. Last Updated: 10 November 2022 [See what's changed](/docs/resources/terms/privacy-policy/recent-changes) | [Previous versions](#previous-versions) ## Previous versions [#previous-versions] * [MATTR Privacy Policy - 9 December 2021 (archived)](/docs/resources/terms/privacy-policy/9-12-21) * [MATTR Privacy Policy - 25 March 2021 (archived)](/docs/resources/terms/privacy-policy/25-3-21) # MATTR Privacy Policy (archived) URL: /docs/resources/terms/privacy-policy/17-10-25 This document is provided for archival purposes only. Last Updated: 17 October 2025 [See what's changed](/docs/resources/terms/privacy-policy/recent-changes) | [Previous versions](#previous-versions) ## Previous versions [#previous-versions] ### Privacy policy [#privacy-policy] * [MATTR Privacy Policy - 5 November 2024 (archived)](/docs/resources/terms/privacy-policy/5-11-24) * [MATTR Privacy Policy - 10 November 2022 (archived)](/docs/resources/terms/privacy-policy/10-11-22) * [MATTR Privacy Policy - 9 December 2021 (archived)](/docs/resources/terms/privacy-policy/9-12-21) * [MATTR Privacy Policy - 25 March 2021 (archived)](/docs/resources/terms/privacy-policy/25-3-21) ### Website privacy policy [#website-privacy-policy] * [MATTR Website Privacy Policy - 9 December 2021 (archived)](/docs/resources/terms/website-privacy-policy/9-12-21) * [MATTR Website Privacy Policy - 20 July 2021 (archived)](/docs/resources/terms/website-privacy-policy/20-7-21) * [MATTR Website Privacy Policy - 25 March 2021 (archived)](/docs/resources/terms/website-privacy-policy/25-3-21) # MATTR Privacy Policy (archived) URL: /docs/resources/terms/privacy-policy/25-3-21 This document is provided for archival purposes only. Last Updated: 25 March 2021 ## Your Privacy Matters [#your-privacy-matters] MATTR is built and operated as a privacy-first company. Enhancement of privacy and trust in digital transactions are fundamental to the software we develop. We aim to support entities participating in the growing eco-system of privacy-preserving verifiable data transactions. We do not sell, rent or otherwise monetise personal information that we process on our customers’ behalf. This privacy policy (**Privacy Policy**) sets out how we collect, use, disclose and protect personal information when: * we provide Services to our customers through our software-as-a-service platform (e.g., when we issue or verify credentials) * we provide other Services to our customers (such as training or seminars), or * we provide access to or use of our Materials, such as apps, SDKs, and the content, materials, software, data, documents (etc.) we make available to allow use of our Services. (For a full definition of all of our Services and Materials, see our [Customer Agreement](/docs/resources/terms/customer-agreement), which may be updated from time to time). First and foremost, we use your personal information to provide our customers with requested Services and Materials and to manage our relationship with you and our customers. Where end users’ personal information is provided to us for the purpose of us providing our Services (e.g. when our customer issues a credential using our Services), we use and disclose that end user personal information for service delivery and the other limited purposes set out in this Privacy Policy. We do not use it for purposes like marketing services from us or others to the end user. If you access or use our website or otherwise use Materials without representing or becoming our customer, the [MATTR Website Privacy Policy](/docs/resources/terms/website-privacy-policy/25-3-21) applies. If you don’t have a relationship with us, but believe your personal information is used by an entity that accesses or uses our Materials or Services, that entity’s privacy policy applies to their collection, use and disclosure of your personal information. In the first instance, we recommend that you contact that entity for any questions you have about your personal information (including where you want to access, correct, amend, or request the deletion of, your personal information). # MATTR Privacy Policy (archived) URL: /docs/resources/terms/privacy-policy/5-11-24 This document is provided for archival purposes only. Last Updated: 5 November 2024 [See what's changed](/docs/resources/terms/privacy-policy/recent-changes) | [Previous versions](#previous-versions) ## Previous versions [#previous-versions] ### Privacy policy [#privacy-policy] * [MATTR Privacy Policy - 10 November 2022 (archived)](/docs/resources/terms/privacy-policy/10-11-22) * [MATTR Privacy Policy - 9 December 2021 (archived)](/docs/resources/terms/privacy-policy/9-12-21) * [MATTR Privacy Policy - 25 March 2021 (archived)](/docs/resources/terms/privacy-policy/25-3-21) ## Website privacy policy [#website-privacy-policy] 1. [MATTR Website Privacy Policy - 9 December 2021 (archived)](/docs/resources/terms/website-privacy-policy/9-12-21) 2. [MATTR Website Privacy Policy - 20 July 2021 (archived)](/docs/resources/terms/website-privacy-policy/20-7-21) 3. [MATTR Website Privacy Policy - 25 March 2021 (archived)](/docs/resources/terms/website-privacy-policy/25-3-21) # MATTR Privacy Policy (archived) URL: /docs/resources/terms/privacy-policy/9-12-21 This document is provided for archival purposes only. Last Updated: 9 December 2021 ## Your Privacy Matters [#your-privacy-matters] MATTR is built and operated as a privacy-first company. Enhancement of privacy and trust in digital transactions are fundamental to the software we develop. We aim to support entities participating in the growing eco-system of privacy-preserving verifiable data transactions. We do not sell, rent or otherwise monetise personal information that we process on our customers’ behalf. This privacy policy (**Privacy Policy**) sets out how we collect, use, disclose and protect personal information when: * we provide Services to our customers through our software-as-a-service platform (e.g., when we issue or verify credentials) * we provide other Services to our customers (such as training or seminars), or * we provide access to or use of our Materials, such as apps, SDKs, and the content, materials, software, data, documents (etc.) we make available to allow use of our Services. (For a full definition of all of our Services and Materials, see our [Customer Agreement](https://learn.mattr.global/docs/resources/terms/customer-agreement), which may be updated from time to time). First and foremost, we use your personal information to provide our customers with requested Services and Materials and to manage our relationship with you and our customers. Where end users’ personal information is provided to us for the purpose of us providing our Services (e.g. when our customer issues a credential using our Services), we use and disclose that end user personal information for service delivery and the other limited purposes set out in this Privacy Policy. We do not use it for purposes like marketing services from us or others to the end user. If you access or use our website, sign-up to receive MATTR communications or content, or otherwise use Materials without representing or becoming our customer, the [MATTR Website Privacy Policy](https://learn.mattr.global/docs/resources/terms/website-privacy-policy/9-12-21) applies. If you don’t have a relationship with us, but believe your personal information is used by an entity that accesses or uses our Materials or Services, that entity’s privacy policy applies to their collection, use and disclosure of your personal information. In the first instance, we recommend that you contact that entity for any questions you have about your personal information (including where you want to access, correct, amend, or request the deletion of, your personal information). # MATTR Privacy Policy URL: /docs/resources/terms/privacy-policy Last Updated: 16 April 2026 [See what's changed](/docs/resources/terms/privacy-policy/recent-changes) | [Previous versions](#previous-versions) ## Previous versions [#previous-versions] ### Privacy policy [#privacy-policy] * [MATTR Privacy Policy - 17 October 2025 (archived)](/docs/resources/terms/privacy-policy/17-10-25) * [MATTR Privacy Policy - 5 November 2024 (archived)](/docs/resources/terms/privacy-policy/5-11-24) * [MATTR Privacy Policy - 10 November 2022 (archived)](/docs/resources/terms/privacy-policy/10-11-22) * [MATTR Privacy Policy - 9 December 2021 (archived)](/docs/resources/terms/privacy-policy/9-12-21) * [MATTR Privacy Policy - 25 March 2021 (archived)](/docs/resources/terms/privacy-policy/25-3-21) ### Website privacy policy [#website-privacy-policy] * [MATTR Website Privacy Policy - 9 December 2021 (archived)](/docs/resources/terms/website-privacy-policy/9-12-21) * [MATTR Website Privacy Policy - 20 July 2021 (archived)](/docs/resources/terms/website-privacy-policy/20-7-21) * [MATTR Website Privacy Policy - 25 March 2021 (archived)](/docs/resources/terms/website-privacy-policy/25-3-21) # MATTR Privacy Policy - What's Changed URL: /docs/resources/terms/privacy-policy/recent-changes ## Changes posted April 16, 2026 [#changes-posted-april-16-2026] MATTR updated the [Privacy Policy](/docs/resources/terms/privacy-policy) to reflect the change from hCaptcha to reCAPTCHA as the anti-bot service used on our website. ## Changes posted October 17, 2025 [#changes-posted-october-17-2025] MATTR updated the [Privacy Policy](/docs/resources/terms/privacy-policy) to remove references to Pardot, Salesforce and 6Sense who are no longer used as part of MATTR's technology stack. ## Changes posted November 5, 2024 [#changes-posted-november-5-2024] MATTR updated the [Privacy Policy](/docs/resources/terms/privacy-policy) to include the (previously separate) Website Privacy Policy and make changes to reflect a number of product and website changes as well as MATTR's continued international expansion. ## Changes posted November 10, 2022 [#changes-posted-november-10-2022] With the addition of customers and users in new regions, MATTR updated the [Privacy Policy](/docs/resources/terms/privacy-policy) to reflect the application of local data protection laws. ## Changes posted December 9, 2021 [#changes-posted-december-9-2021] MATTR updated the [Privacy Policy](/docs/resources/terms/privacy-policy) to include reference to the Website Privacy Policy. ## Changes posted March 25, 2021 [#changes-posted-march-25-2021] MATTR updated the [MATTR Privacy Policy](/docs/resources/terms/privacy-policy) on March 25, 2021. The [MATTR Privacy Policy](/docs/resources/terms/privacy-policy) was published. Your use of our Services after the effective date of this update confirms that you have read and understood the updated MATTR Privacy Policy. # MATTR Pi SDK Trial Licence Agreement URL: /docs/resources/terms/sdk-licence-agreement Last Updated: 16 Dec 2024 **IMPORTANT**: THIS MATTR Pi SDK TRIAL LICENCE AGREEMENT APPLIES TO USE OF ALL OR ANY PART OF ANY SOFTWARE DEVELOPMENT KITS SPECIFIED IN ANNEX A OR WHICH SPECIFY THEY ARE SUBJECT TO THIS AGREEMENT. BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING OR ACCESSING A SOFTWARE DEVELOPMENT KIT SUBJECT TO THIS AGREEMENT, YOU AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. IF YOU DO NOT ACCEPT THIS AGREEMENT YOU MUST NOT DOWNLOAD, INSTALL, COPY OR OTHERWISE USE OR ACCESS THE SOFTWARE. I agree (on behalf of my organisation, if applicable) to the terms of this SDK Agreement and the Customer Agreement: **Customer Name**: **Signature**: **Date:** **Name**: **Title**: ## ANNEX A: SOFTWARE DEVELOPMENT KITS SUBJECT TO THIS AGREEMENT [#annex-a-software-development-kits-subject-to-this-agreement] All MATTR Pi Software Development Kits and any of the following: * All MATTR Verifier SDKs, including any version, variant, additional component, add-on and/or library (whether beta, tech preview or general availability), for any platform, language or operating system (e.g. web, iOS or React Native) or any credential type; and * All MATTR Wallet or Hold SDKs, including any version, variant, additional component, add-on and/or library (whether beta, tech preview or general availability), for any platform, language or operating system (e.g. iOS or React Native) or any credential type, in each case except to the extent alternative licence terms are agreed with us for that SDK. # MATTR Service Level Agreement URL: /docs/resources/terms/service-level-agreement Last Updated: 2 July 2025 MATTR offers a range of support options to meet your needs. For further information, please [contact us](https://mattr.global/contact-us). # MATTR Service Level Agreement URL: /docs/resources/terms/service-level-agreement/sla *Last Updated: 25 March 2021* This MATTR Service Level Agreement (**SLA**) governs your use of the public cloud deployment of the Services. It forms part of the Agreement defined in the MATTR Customer Agreement document. Capitalised terms used but not defined in this SLA have the meanings set out in the MATTR Customer Agreement document. Other definitions are set out below. If you require an SLA commitment or alternative deployment options, such as a dedicated instance of the Services (private cloud, on-premises, etc.) please contact us. # MATTR Website Privacy Policy (archived) URL: /docs/resources/terms/website-privacy-policy/20-7-21 This document is provided for archival purposes only. Last Updated: 20 July 2021 MATTR is built and operated as a privacy-first company. Enhancing privacy and trust in digital transactions is part of our DNA. This privacy policy explains how we collect, use and disclose personal information and applies when you access or use our website, or if you are not representing one of our customers and you communicate with us (e.g., through community forums), follow or interact with us on social media, or take part in training and events (**Applicable Use**). We have a separate privacy policy that applies when you access or use our services or materials on behalf of one of our customers (e.g., our software, platforms and associated materials) (**Services**). This can be accessed [here.](/docs/resources/terms/privacy-policy) ## Previous versions [#previous-versions] 1. [Privacy Policy - 25 March 2021 (archived)](/docs/resources/terms/website-privacy-policy/25-3-21) # MATTR Website Privacy Policy (archived) URL: /docs/resources/terms/website-privacy-policy/25-3-21 This document is provided for archival purposes only. Last Updated: 25 March 2021 [See what's changed](/docs/resources/terms/website-privacy-policy/recent-changes) | [Previous versions](#previous-versions) MATTR is built and operated as a privacy-first company. Enhancing privacy and trust in digital transactions is part of our DNA. This privacy policy explains how we collect, use and disclose personal information and applies when you access or use our website, or if you are not representing one of our customers and you communicate with us (e.g., through community forums), follow or interact with us on social media, or take part in training and events (**Applicable Use**). We have a separate privacy policy that applies when you access or use our services or materials on behalf of one of our customers (e.g., our software, platforms and associated materials) (**Services**). This can be accessed [here.](/docs/resources/terms/privacy-policy) # MATTR Website Privacy Policy (archived) URL: /docs/resources/terms/website-privacy-policy/9-12-21 This document is provided for archival purposes only. Last Updated: 9 December 2021 [See what's changed](/docs/resources/terms/website-privacy-policy/recent-changes) | [Previous versions](#previous-versions) MATTR is built and operated as a privacy-first company. Enhancing privacy and trust in digital transactions is part of our DNA. This privacy policy explains how we collect, use and disclose personal information and applies when you access or use our website, or if you are not representing one of our customers and you communicate with us (e.g., through community forums or if you sign-up to receive MATTR communications or content), follow or interact with us on social media, or take part in training and events (**Applicable Use**). We have a separate privacy policy that applies when you access or use our services or materials on behalf of one of our customers (e.g., our software, platforms and associated materials) (**Services**). This can be accessed  [here.](/docs/resources/terms/privacy-policy) ## Previous versions [#previous-versions] 1. [Privacy Policy - 20 July 2021 (archived)](/docs/resources/terms/website-privacy-policy/20-7-21) 2. [Privacy Policy - 25 March 2021 (archived)](/docs/resources/terms/website-privacy-policy/25-3-21) # Website Privacy Policy - What's Changed URL: /docs/resources/terms/website-privacy-policy/recent-changes By using the Website after the “Last updated” date at the top of our [Privacy Policy](/docs/resources/terms/privacy-policy), you are deemed to have accepted the updated Terms. ## Changes posted December 9, 2021 [#changes-posted-december-9-2021] MATTR updated the [Website Privacy Policy](/docs/resources/terms/website-privacy-policy/9-12-21) on December, 9 2021 * Inclusion of first-party cookies tracking with Pardot and Google Ads. ## Changes posted July 20, 2021 [#changes-posted-july-20-2021] MATTR updated the [Website Privacy Policy](/docs/resources/terms/website-privacy-policy/20-7-21) on July 20, 2021. * Changed web analytics solution from Fathom to Matomo. ## Changes posted March 25, 2021 [#changes-posted-march-25-2021] MATTR updated the [Website Privacy Policy](/docs/resources/terms/website-privacy-policy/25-3-21) on March 25, 2021. * The Website Privacy Policy was published. # Website Terms of Use URL: /docs/resources/terms/website-terms-of-use Last Updated: 25 March 2021 [See what's changed](/docs/resources/terms/website-terms-of-use/recent-changes) | [Previous versions](#previous-versions) ## Welcome to the MATTR website! [#welcome-to-the-mattr-website] These Website Terms of Use (Terms) are agreed between MATTR Limited (**we**, **us**, or **our**) and you or the entity you represent, as applicable (**you** or **your**) and apply only when you access or use our Site (**Website**). Different terms apply when you access or use our Services (e.g., our software, platforms and associated materials) (**Services**). These other terms can be accessed [here](/docs/resources/terms/customer-agreement). By accessing or using our Website, you acknowledge that you have read and accepted these Terms. We may update or replace these Terms from time to time by publishing a new version on our Website. By using the Website after the “Last updated” date at the top of these Terms, you are deemed to have accepted the updated Terms. If you don’t agree to the updated Terms, you must stop accessing and using the Website. # Website Terms of Use - What's Changed URL: /docs/resources/terms/website-terms-of-use/recent-changes ## Changes posted March 25, 2021 [#changes-posted-march-25-2021] MATTR updated the [Website Terms of Use](/docs/resources/terms/website-terms-of-use) on March 25, 2021. The [Website Terms of Use](/docs/resources/terms/website-terms-of-use) were published. By using the Website after the “Last updated” date at the top of these Terms, you are deemed to have accepted the updated Terms. # Android Holder SDK v6.0.0 Migration Guide URL: /docs/holding/sdk-operations/migration-guides/android-6.0.0-migration-guide ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in MobileCredentialHolderSDK v6.0.0 for Android, including breaking changes, new features, and migration steps. ## Key Features [#key-features] * **Seamless, OS-native credential presentation (DC API support)**: The Android Holder SDK now integrates with the Digital Credentials API, allowing credentials to be presented via an OS overlay without launching your holder app. The OS automatically surfaces only matching credentials across installed wallets — reducing friction, avoiding dead ends, and significantly improving the user experience. * **Clearer OID4VCI interoperability (v1.0 alignment)**: The Android Holder SDK now aligns with the finalized OID4VCI v1.0 specification (upgraded from draft-12). This alignment improves interoperability across the ecosystem, enabling other systems to clearly identify your supported version and feature set, ensuring smoother integrations and consistent behavior across platforms. * **Improved reliability in contactless flows**: Enhanced BLE performance delivers more consistent proximity credential exchanges and faster engagements. * **Stronger cryptography and standards alignment**: Updated COSE algorithms (as per RFC 9864) strengthen cryptographic compatibility and ensure continued compliance with evolving standards. * **Simpler, Decoupled Releases**: The Android Holder SDK no longer has a shared common module. This allows us to decouple the releases of Holder and Verifier and versions no longer need to match. * **Improved UI in NFC presentations**: The SDK now provides enhanced handling of system UI prompts on devices that support multiple NFC-enabled mobile credential wallets, ensuring clearer wallet selection and a smoother presentation flow. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency across mobile environments, and improve overall user experience. For a detailed list of changes included in this release, refer to the [SDK Changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/). ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v6.0.0 that require updates to your existing implementation: | # | Element | Change | Impact | | - | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | 1 | `MobileCredentialHolder.getCredential(credentialId,skipStatusCheck = ...)` | Parameter renamed to `fetchUpdatedStatusList = ...` with **inverted semantics** | All call sites using `skipStatusCheck = ...` must be updated. | | 2 | `OfferedCredential` | New required property `credentialConfigurationId: String` | Manual decoders must handle new field. | | 3 | `VerifierAuthenticationResult` | New subclass `Unsigned(val origin: String?)` | Exhaustive `when` statements must add new case. | | 4 | `MobileCredentialRequest.verifierAuthenticationResult` | No longer nullable | Exhaustive `when` statements, or null checks, must now handle `Unsigned` in place of `null`. | | 5 | `global.mattr.mobilecredential.common` | Renamed `global.mattr.mobilecredential.holder` | All uses of any `common` must be updated to use `holder`. | | 6 | `MobileCredentialHolder.NFC` | NFC Activity is now launched automatically by the SDK | Calls to `MobileCredentialHolder.Nfc.setDeviceEngagementListener` must be updated to align with the new automatic Activity handling. | ## New Additions [#new-additions] ### Types [#types] | Type | Purpose | | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `DcmConfiguration` | Configure Digital Credentials support (enable/disable) | | `MobileCredentialHolder.Nfc.onActivityResume` | Notifies Android that your `Activity` should be treated as the preferred NFC handler while it is in the foreground. | | `MobileCredentialHolder.Nfc.onActivityPause` | Notifies Android that your `Activity` should no longer be treated as the preferred NFC handler when it leaves the foreground. | ### Sealed & Enum Classes [#sealed--enum-classes] | Type | New Case | | ------------------------------ | ------------------------------- | | `VerifierAuthenticationResult` | `Unsigned(val origin: String?)` | ### New Dependencies [#new-dependencies] * `androidx.credentials.registry:registry-provider` * `androidx.credentials.registry:registry-provider-play-services` ## Bug Fixes [#bug-fixes] * Fixed parsing of status lists that use a bit size other than 2. * Resolved BLE connection stalls on Pixel 7 Pro devices. * Resolved NFC engagement issues on Pixel 6 Pro devices. * Aligned WebView OID4VCI error messages for improved consistency. ## Minimum Requirements [#minimum-requirements] * Android 7 / Nougat / API 24. * The Android Holder SDK is built using Kotlin 2.0. This adds some intrinsic dependencies into your build tools. * Kotlin 2.0 is supported from AGP version [8.5](https://developer.android.com/build/kotlin-support). * AGP 8.5 is supported from Gradle version [8.7](https://developer.android.com/build/releases/about-agp) and Android Studio Koala [2024.1.1](https://developer.android.com/studio/releases). ## Migration Steps [#migration-steps] ### Rename any references to the `common` dependency to `holder` [#rename-any-references-to-the-common-dependency-to-holder] The shared `common` module has been removed and the Android Holder SDK is now published as `holder`. This means that any references to `global.mattr.mobilecredential.common` in your codebase should be updated to `global.mattr.mobilecredential.holder`. This can be done with a global find and replace across your codebase. If you are using both the Holder and Verifier SDKs, you will need to limit your search to the relevant parts of your application. ### Update `getCredential` calls [#update-getcredential-calls] The `skipStatusCheck` parameter has been renamed to `fetchUpdatedStatusList` with inverted semantics. When `fetchUpdatedStatusList` is `true` (default), the SDK will fetch the latest status list to ensure up-to-date revocation information. When `false`, it will skip fetching the status list and rely on cached data (when valid). Update all calls accordingly: ```diff - val credential = holder.getCredential(credentialId = id, skipStatusCheck = true) + val credential = holder.getCredential(credentialId = id, fetchUpdatedStatusList = false) ``` | Old Parameter | New Parameter | | ----------------------------------- | ----------------------------------------- | | `skipStatusCheck = false` (default) | `fetchUpdatedStatusList = true` (default) | | `skipStatusCheck = true` | `fetchUpdatedStatusList = false` | Refer to [Revocation Status check](/docs/holding/credential-claiming-guides/revocation-status-check) for more information. ### Handle `OfferedCredential` changes [#handle-offeredcredential-changes] The `OfferedCredential` data class now includes a new required property `val credentialConfigurationId: String`. If you have custom encoding or decoding logic for this object, make sure it is updated to handle the new field: ```diff data class OfferedCredential( val doctype: String, val claims: List, val name: String?, + val credentialConfigurationId: String, internal val scope: String ) ``` ### Handle `VerifierAuthenticationResult` changes [#handle-verifierauthenticationresult-changes] The `VerifierAuthenticationResult` sealed class now includes a new subclass `Unsigned(val origin: String?)` to represent unsigned/unauthenticated requests. If you have any exhaustive `when` statements on `VerifierAuthenticationResult`, you must add a new case to handle this scenario: ```diff val trusted = when (verifierAuthenticationResult) { is Trusted -> "Trusted verifier" is Untrusted -> "Authentication failed") - else -> + is Unsigned -> "No authentication" } ``` ### Handle NFC device engagement changes [#handle-nfc-device-engagement-changes] Starting with Android Holder SDK v6.0.0, the SDK automatically launches an Activity when NFC device engagement occurs. Manual Activity launching from your NFC engagement listener is no longer required. To migrate your implementation, make the following changes: 1. In your AndroidManifest.xml, add the following intent-filter to the Activity that should be started (or brought to the foreground) when NFC device engagement begins: ```xml ``` Any Activity that declares this intent filter will be automatically launched by the SDK when NFC device engagement starts. 2. Remove any manual Activity launching logic from your NFC engagement listener. 3. Your NFC device engagement listener should now only handle UI updates (for example, displaying your NFC screen). It should no longer start an `Activity`. It is recommended to register the listener in the NFC Activity’s `onCreate()` method (or in a related `ViewModel`), rather than in your `Application` class: ```diff -class YourApplication : Application() { +class NfcActivity : Activity() { override fun onCreate() { super.onCreate() MobileCredentialHolder.Nfc.setDeviceEngagementListener { - startNfcActivity() + showNfcUI() } } } ``` 4. To improve the user experience on devices with multiple NFC-capable mobile credential wallets, you can **optionally** explicitly mark your `Activity` as the preferred NFC handler while it is in the foreground: ```kotlin override fun onResume() { super.onResume() MobileCredentialHolder.Nfc.onActivityResume(this) } override fun onPause() { MobileCredentialHolder.Nfc.onActivityPause(this) super.onPause() } ``` This ensures your `Activity` is prioritised for NFC handling while visible. ### Enable Presenting Credentials via the Digital Credentials API (Optional) [#enable-presenting-credentials-via-the-digital-credentials-api-optional] The Android Holder SDK v6.0.0 introduces support for the Digital Credentials API, allowing credentials to be presented via an OS-native overlay without launching your app. To enable this feature, you need to initialize the SDK with a `DcmConfiguration` that sets enabled to `true`. This is optional but recommended for a seamless user experience. ```diff MobileCredentialHolder.getInstance().initialize( // ... + dcmConfiguration = DcmConfiguration(enabled = true) ) ``` ### Enable Confirmation and Error Handling for the Digital Credentials API (Optional) [#enable-confirmation-and-error-handling-for-the-digital-credentials-api-optional] The Digital Credentials API support includes new intents for handling confirmation and error scenarios during credential presentation. To enable this, you need to add intent filters to your AndroidManifest.xml and handle the incoming intents in your activity: ```xml ``` ```kotlin val verifierAuthenticationResult = IntentCompat.getParcelableExtra( intent, "global.mattr.credentialmanager.extra.verifier_authentication_result", VerifierAuthenticationResult::class.java ) ``` ```xml ``` # Android Holder SDK v7.0.0 Migration Guide URL: /docs/holding/sdk-operations/migration-guides/android-7.0.0-migration-guide Description: A comprehensive guide to migrating to Android Holder SDK v7.0.0, covering breaking changes, new features, and step-by-step migration instructions. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in the Android Holder SDK v7.0.0, including breaking changes, new features, and migration steps. This release focuses on strengthening trust at issuance, improving predictability in credential handling, and increasing consistency across platforms. It introduces optional SDK Tethering to connect the SDK to your MATTR VII tenant, adds optional Wallet Attestation support so issuers can verify wallet integrity before issuing credentials, and strengthens validation and resilience in credential flows. SDK Tethering (and Wallet Attestation, which builds on it) is **optional** in this release. You opt in by passing a `platformConfiguration` when initializing the SDK. If you omit it, the SDK skips registration, tethering, and wallet attestation, and existing integrations continue to work. We expect to make SDK Tethering required in an upcoming release, so we recommend adopting it now to prepare. ## Key Features [#key-features] * **SDK Tethering (optional)**: The Android Holder SDK can now be tethered to a MATTR VII tenant, tying each SDK/app instance to your tenant. This allows you to view details about registered and active app instances directly from your tenant for operational insights. SDK Tethering also establishes a remote management channel that we expect to extend in the future with additional features, such as remote syncing of trusted issuer lists and eventing. Tethering is enabled by providing a `platformConfiguration` at initialization. * **Wallet Attestation support (optional)**: Building on the SDK Tethering channel, the Android Holder SDK now supports Wallet Attestation, allowing your holder application to prove to issuers that it is a trusted wallet before credentials are issued. When an issuer requires it, the SDK uses the tethering connection to obtain wallet attestation tokens from your MATTR VII tenant. Wallet Attestation requires SDK Tethering to be configured. * **Stronger application identity during issuance**: Pre-authorized credential issuance flows now pass the application's `client_id`, ensuring the holder is accurately represented when interacting with issuers. This improves compatibility with issuers applying stricter controls. * **More predictable credential retrieval results**: Credential retrieval responses are now more structured and deterministic, explicitly identifying success or failure with guaranteed fields per result type. * **Stronger validation in presentation flows**: Stricter JWT request validation, state size validation, and certificate chain validation improve how the SDK handles malformed or incomplete requests. * **Improved resilience in issuance flows**: Oversized credential payload validation prevents failure modes that could lead to degraded user experiences. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency, and improve overall reliability. ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v7.0.0 that require updates to your existing implementation: | # | Change | Impact | | -- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | `UserAuthenticationConfiguration` now takes a `userAuthenticationType` parameter | Configure biometric authentication behavior. `initialize` may throw `HolderException.UserAuthenticationNotSupportedException` if you use `BiometryCurrentSet` with `OnInitialize`. | | 2 | Pre-authorized code flow now uses the application's `client_id` instead of a default identifier | Ensure your application has a valid configured `client_id` and issuers recognize it. | | 3 | Credential retrieval result shape updated to explicitly identify success or failure with guaranteed fields | Update result parsing logic to use success/failure branching. | | 4 | `OfferedCredential.doctype` renamed to `docType` | Rename all usages of `doctype` to `docType`. | | 5 | `AuthenticationOption` renamed to `DeviceAuthenticationOption` (and the `authenticationOption` parameter on `createProximityPresentationSession` renamed to `deviceAuthenticationOption`) | Update all imports, type references, and the session-creation argument name. | | 6 | `OfferedCredential.claims` is now optional | Handle the case where `claims` is `null`, indicating no claim data is included in the offer. | | 7 | `RetrieveCredentialResult` is now a sealed interface with `Success` and `Failure` variants | Replace field-based branching with `when`/`is` pattern matching. | | 8 | `OnlinePresentationSession.matchedCredentials` is now the `getMatchedCredentials()` method | Replace property access with the method call. | | 9 | `sessionStatus` parameter removed from `ProximityPresentationSession.terminateSession` | Listen for `SessionStatus.SessionTerminated` in your presentation session callback instead of passing a custom status. | | 10 | Required `authorizationServerIssuer` and `tokenEndpointAuthMethodsSupported` fields added to `DiscoveredCredentialOffer` | Supply the new arguments if you construct this type directly. | | 11 | `ConnectivityError` and `InvalidWalletAttestation` added to `RetrieveCredentialError` | Handle the new cases in exhaustive `when` statements. | | 12 | Stricter JWT request validation for online presentations: enforces expected `typ`, validates `iat`, rejects expired `exp` | Where possible, ensure verifiers generate compliant JWT authorization request objects. | | 13 | Oversized `state` values in VP online presentation requests are now rejected | Where possible, ensure verifier-generated `state` values remain within supported limits. | | 14 | Oversized credential fields in pre-authorized issuance are now rejected | Where possible, validate issuer payload sizes and test with realistic credential data. | | 15 | `mdocIacasUri` is now optional | Review assumptions that this field is always present. | ## Migration Steps [#migration-steps] ### Configure `userAuthenticationType` [#configure-userauthenticationtype] `UserAuthenticationConfiguration` now takes a `userAuthenticationType` parameter to configure biometric authentication behavior, aligning with iOS. The default authentication type is `UserPresence`. Review your configuration and note that `initialize` may now throw `HolderException.UserAuthenticationNotSupportedException` if you attempt to use `BiometryCurrentSet` with `OnInitialize`: ```diff val userAuthConfig = UserAuthenticationConfiguration( userAuthenticationBehavior = OnDeviceKeyAccess, + userAuthenticationType = UserPresence ) ``` `UserPresence` matches the implicit behavior of v6.x, so existing integrations are unaffected if you keep it. Opt in to `BiometryCurrentSet` only if you require that stricter behavior. ### (Optional) Create a holder application on your MATTR VII tenant [#optional-create-a-holder-application-on-your-mattr-vii-tenant] SDK Tethering is **optional** in this release. If you do not need it, you can skip this step and the next one. The SDK continues to function without a `platformConfiguration`. To enable SDK Tethering, the Android Holder SDK connects to a MATTR VII tenant. Tethering gives you access to a centralized view of registered and active app instances in MATTR VII, the optional Wallet Attestation feature, and other centralized management capabilities we plan to deliver over this channel. We expect SDK Tethering to become required in an upcoming release. We recommend adopting it now to prepare, even if you do not yet use Wallet Attestation. To register your Android application, make a request to create a holder application: ```http title="Request" POST /v1/holder/applications ``` ```json title="Request body" { "name": "My Android Holder Application", "clientId": "your-wallet-client-id", "type": "android", "packageName": "com.yourcompany.holderapp", "packageSigningCertificateThumbprints": [ "1232584b6f6a892d356899fb9576c5f226a179e6199f2b7a1d837b5c234c5a8e" ] } ``` * `name`: A unique name to identify your holder application. * `clientId`: The OAuth 2.0 `client_id` that identifies your wallet application. This value is included in attestation JWTs and must match the `client_id` configured on the issuer's Authorization Server. * `type`: Must be `android` for an Android application. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/holding/android-app-signing) for more information. Once created, your holder application will be able to use the SDK and interact with the MATTR VII platform (for example, to obtain attestation tokens). The response will include a unique `id` for your application, which must be used when initializing the SDK so that it can correctly identify and authenticate your application. The `clientId` you configure here is the same value you must: 1. Pass as the `clientId` parameter when calling [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/retrieve-credentials.html). 2. Register with each issuer you intend to interact with, so the issuer can identify and trust requests coming from your wallet application. ### (Optional) Enable tethering at SDK initialization [#optional-enable-tethering-at-sdk-initialization] To enable SDK Tethering, pass a `platformConfiguration` when initializing the SDK. This parameter is optional. Omit it to initialize the SDK without tethering: ```diff - MobileCredentialHolder.initialize(context, instanceId) + val platformConfig = PlatformConfiguration( + tenantHost = URL("https://your-tenant.vii.mattr.global"), + applicationId = "1ef1f867-20b4-48ea-aec1-bea7aff4964c" + ) + MobileCredentialHolder.initialize(context, instanceId, platformConfiguration = platformConfig) ``` * `tenantHost`: The URL of your MATTR VII tenant where your holder application is configured. * `applicationId`: The `id` of your configured MATTR VII holder application. When `platformConfiguration` is omitted, the SDK skips registration, tethering, and wallet attestation. ### Handle new error cases [#handle-new-error-cases] This release adds two new values to `RetrieveCredentialError`, `ConnectivityError` and `InvalidWalletAttestation`. This is a **breaking change** that requires updates to exhaustive `when` blocks over this enum regardless of whether you enable SDK Tethering. If you enable SDK Tethering and Wallet Attestation, the following exceptions on `HolderException` also become relevant during credential issuance: * `HolderException.InvalidCredentialOfferException`: The offer requires attestation but no `platformConfiguration` was provided, or the SDK does not support any of the client authentication methods advertised by the authorization server. * `HolderException.WalletAttestationFailedException`: The SDK could not obtain an attestation token from the MATTR VII tenant. * `HolderException.InvalidWalletAttestationException`: The authorization server rejected the attestation token. The [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/retrieve-credentials.html) method may now throw these exceptions. Wallet attestation errors only occur when the SDK is tethered and an issuer requires attestation. Update your error handling, logging, analytics, and support diagnostics to account for these new error cases. ### Update `client_id` configuration [#update-client_id-configuration] Previously, the `client_id` passed to [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/retrieve-credentials.html) was not shared with the issuer during the pre-authorized code flow, so any value would work. This is no longer the case. The SDK now presents the `client_id` to the issuer as part of wallet attestation, and the issuer validates it against its list of trusted wallet providers. To prepare for this change: 1. **Coordinate with the issuer** to register your wallet application as a trusted wallet provider. The issuer will provide you with a `client_id` that identifies your application. 2. **Pass the issuer-provided `client_id`** when calling `retrieveCredentials`. ```diff val results = holder.retrieveCredentials( activity = activity, credentialOffer = offer, - clientId = "any-value", + clientId = "issuer-provided-client-id" ) ``` Issuance flows that previously worked with an arbitrary `client_id` will fail if the issuer requires a trusted wallet provider. Ensure you have coordinated with each issuer and obtained the correct `client_id` before upgrading. Test direct issuance flows to confirm credentials are issued successfully. ### Update credential retrieval result handling [#update-credential-retrieval-result-handling] `RetrieveCredentialResult` has been converted from a data class with nullable fields to a sealed interface with explicit `Success` and `Failure` subtypes. Each subtype carries guaranteed properties, removing ambiguity from result handling. The `retrieveCredentials` function returns `List`, a list with one result per offered credential. Update your iteration logic to use `when` matching: ```diff val results = holder.retrieveCredentials(options) for (result in results) { - val credentialId = result.credentialId - if (credentialId != null) { - // Use result.docType and credentialId - } else { - // Use result.docType and result.error - } + when (result) { + is RetrieveCredentialResult.Success -> { + // result.docType and result.credentialId are guaranteed + } + is RetrieveCredentialResult.Failure -> { + // result.docType and result.error are guaranteed + } + } } ``` Update tests and any downstream logic that checked for `null` fields. ### `doctype` renamed to `docType` [#doctype-renamed-to-doctype] The `doctype` field has been renamed to `docType` (camelCase). This affects: * [`OfferedCredential`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder.issuance.dto/-offered-credential/index.html) returned by [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-android/latest/m-docs%20-holder%20-s-d-k/global.mattr.mobilecredential.holder/-mobile-credential-holder/discover-credential-offer.html) Update all references from `.doctype` to `.docType`. ```diff - val documentType = credential.doctype + val documentType = credential.docType ``` ### Rename `AuthenticationOption` to `DeviceAuthenticationOption` [#rename-authenticationoption-to-deviceauthenticationoption] `AuthenticationOption` has been renamed to `DeviceAuthenticationOption`. Update all imports and type references, and rename the `authenticationOption` argument on `createProximityPresentationSession` to `deviceAuthenticationOption`: ```diff - import global.mattr.mobilecredential.holder.AuthenticationOption + import global.mattr.mobilecredential.holder.DeviceAuthenticationOption - val authOption: AuthenticationOption = ... + val authOption: DeviceAuthenticationOption = ... - holder.createProximityPresentationSession(authenticationOption = authOption) + holder.createProximityPresentationSession(deviceAuthenticationOption = authOption) ``` ### Handle optional `OfferedCredential.claims` [#handle-optional-offeredcredentialclaims] `OfferedCredential.claims` is now optional and only returned for offers that contain claim data. Handle the case where `claims` is `null`: ```diff - val claims = offeredCredential.claims + val claims = offeredCredential.claims // may be null when the offer contains no claim data + if (claims != null) { + // Use the claims + } ``` ### Update `matchedCredentials` handling for online presentation [#update-matchedcredentials-handling-for-online-presentation] `OnlinePresentationSession.matchedCredentials` has been changed from a property to a `getMatchedCredentials()` method to support Just In Time issuance flows. Replace property access with the method call: ```diff - val matched = session.matchedCredentials + val matched = session.getMatchedCredentials() ``` ### Update `terminateSession` calls [#update-terminatesession-calls] The `sessionStatus` parameter has been removed from `ProximityPresentationSession.terminateSession`. Sessions terminated this way send `SessionStatus.SessionTerminated` to the verifier. If you previously passed a custom status, listen for `SessionStatus.SessionTerminated` in your presentation session callback and handle it as needed: ```diff - session.terminateSession(sessionStatus = customStatus) + session.terminateSession() ``` ### Supply new `DiscoveredCredentialOffer` fields [#supply-new-discoveredcredentialoffer-fields] The required fields `authorizationServerIssuer` and `tokenEndpointAuthMethodsSupported` have been added to `DiscoveredCredentialOffer`. If you construct this type directly, supply the new arguments. Most callers receive this type from `discoverCredentialOffer` and are not affected. ### Update verifier JWT request objects [#update-verifier-jwt-request-objects] The SDK now enforces stricter JWT request validation for online presentations: * The `typ` header must match the expected value. * The `iat` (issued at) claim is now validated. * Expired `exp` (expiration) values are now rejected. Where possible, ensure all verifiers in your ecosystem generate compliant JWT authorization request objects. Update test fixtures and any non-compliant verifier integrations. ### Validate `state` values in presentation requests [#validate-state-values-in-presentation-requests] Oversized `state` values in VP online presentation requests are now rejected. Where possible, ensure verifier-generated `state` values remain within supported limits. Update tests that use large state payloads. ### Ensure `x5c` certificate chains are present [#ensure-x5c-certificate-chains-are-present] Missing `x5c` certificate chains in validity-signed JWT request objects are now safely rejected. Where possible, ensure verifier request objects include required certificate chains where applicable. Update negative-path handling for rejected presentation requests. ### Validate credential payload sizes [#validate-credential-payload-sizes] Oversized credential fields in pre-authorized issuance are now rejected. Where possible, validate issuer payload sizes and test issuance with realistic credential data. Update error handling for rejected oversized credentials: ```diff val result = holder.retrieveCredentials(options) when (result) { is RetrieveCredentialsResult.Failure -> { when (result.error) { // ... existing error cases + is OversizedCredentialPayload -> { + // Handle oversized credential rejection + } } } } ``` ### Handle optional `mdocIacasUri` [#handle-optional-mdociacasuri] The `mdocIacasUri` field is now optional. Review any app-side assumptions that this field is always present and confirm handling of empty string values: ```diff - val iacasUri = credential.mdocIacasUri // assumed non-null + val iacasUri = credential.mdocIacasUri // may be empty string + if (iacasUri.isNotEmpty()) { + // Use the URI + } ``` # iOS Holder SDK v5.0.0 Migration Guide URL: /docs/holding/sdk-operations/migration-guides/ios-5.0.0-migration-guide ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in MobileCredentialHolderSDK v5.0.0 for iOS, including breaking changes, new features, and migration steps. ## Key Features [#key-features] * **Seamless, OS-native credential presentation (DC API support)**: The iOS Holder SDK now integrates with the Digital Credentials API, allowing credentials to be presented via an OS overlay without launching your holder app. The OS automatically surfaces only matching credentials across installed holders — reducing friction, avoiding dead ends, and significantly improving the user experience. * **Clearer OID4VCI interoperability (v1.0 alignment)**: The iOS Holder SDK now aligns with the finalized OID4VCI v1.0 specification (upgraded from draft-12). This alignment improves interoperability across the ecosystem, enabling other systems to clearly identify your supported version and feature set, ensuring smoother integrations and consistent behavior across platforms. * **Improved reliability in contactless flows**: Enhanced BLE performance delivers more consistent proximity credential exchanges and faster engagements. * **Stronger cryptography and standards alignment**: Updated COSE algorithms (as per RFC 9864) strengthen cryptographic compatibility and ensure continued compliance with evolving standards. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency across mobile environments, and improve overall user experience. For a detailed list of changes included in this release, refer to the [SDK Changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/changelog). ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v5.0.0 that require updates to your existing implementation: | # | Element | Change | Impact | | - | --------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | 1 | `MobileCredentialHolder.getCredential(credentialId:skipStatusCheck:)` | Parameter renamed to `fetchUpdatedStatusList:` with **inverted semantics** | All call sites using `skipStatusCheck:` must be updated. | | 2 | `MobileCredentialHolder.deinitialize()` | No longer `async` | Remove `await` from all call sites. | | 3 | `MobileCredentialHolder.initialize(...)` | New `dcConfiguration:` param + `@available(iOSApplicationExtension, unavailable)` | Extensions must use new `initializeAppExtension`. | | 4 | `OfferedCredential` | New required property `credentialConfigurationId: String` | Manual decoders must handle new field. | | 5 | `getCurrentLogFilePath()` | New `appGroup:` param + unavailable in extensions | Source compatible in apps; breaks extensions. | | 6 | `VerifierAuthenticationResult` | New enum case `.unsigned(origin: String?)` | Exhaustive `switch` statements must add new case. | | 7 | 20+ methods (see list below) | Now `@available(iOSApplicationExtension, unavailable)` | Extensions must migrate to DC APIs. | | 8 | Xcode / Toolchain | SDK built with Xcode 26.0.0 | Requires Xcode 26.0.0+. Builds will fail on earlier toolchains (e.g. Xcode 16.4). CI environments must be upgraded. | The following methods are **unavailable** in App Extensions: * Initialization and lifecycle: * `initialize` * `deinitialize` * `destroy` * Credential claiming: * `discoverCredentialOffer` * `createAuthorizationSession` * `retrieveCredentials` * Credential management: * `addCredential` * `deleteCredential` * `getCredentials` * `getCredential` * Key and certificate management: * `generateDeviceKey` * `addTrustedIssuerCertificates` * `getTrustedIssuerCertificates` * `deleteTrustedIssuerCertificate` * `addTrustedVerifierCertificates` * `getTrustedVerifierCertificates` * `deleteTrustedVerifierCertificate` * Presentation sessions: * `createProximityPresentationSession` * `getCurrentProximityPresentationSession` * `createOnlinePresentationSession` * `ProximityPresentationSession.sendResponse` * `terminateSession` * `OnlinePresentationSession.sendResponse` * Logging: * `getCurrentLogFilePath` ## New Additions [#new-additions] ### Types (iOS 26+) [#types-ios-26] | Type | Purpose | | ---------------------------------- | ------------------------------------------------------------------- | | `DCConfiguration` | Configure Digital Credentials support (appGroup, supportedDocTypes) | | `DCConfiguration.SupportedDocType` | Enum: `.mDL`, `.eudi`, `.euav`, `.photoid`, `.jpMnc` | | `DCError` | Errors for DC operations | | `DCPresentationSession` | Handle system-initiated credential requests | ### Methods [#methods] | Method | Availability | Purpose | | ------------------------------------------------------------------ | ------------ | ----------------------------------------------------------------- | | `initializeAppExtension(instanceID:appGroup:loggerConfiguration:)` | iOS 26+ | Initialize SDK in Identity Document Provider extension | | `createDcPresentationSession(from:)` | iOS 26+ | Create session from system `ISO18013MobileDocumentRequestContext` | | `performRegistrationUpdates()` | iOS 26+ | Sync credentials with system Identity Document Store | ### Enum Cases [#enum-cases] | Type | New Case | | ------------------------------ | --------------------------------- | | `VerifierAuthenticationResult` | `.unsigned(origin: String?)` | | `MobileCredentialHolderError` | `.storageInitializedInBackground` | ### Protocol Conformances [#protocol-conformances] | Type | Added Conformance | | ----------------------------- | ----------------- | | `VerifierAuthenticationError` | `Encodable` | ### Framework Imports [#framework-imports] ```swift import IdentityDocumentServices // iOS 26+ import IdentityDocumentServicesUI // iOS 26+ ``` ## Backwards Compatible Changes [#backwards-compatible-changes] | Change | Note | | ---------------------------------------------- | -------------------------------------------- | | `MobileCredentialDataTypes` nested typealiases | `@available` removed (inherited from parent) | | `OnlinePresentationSession.VerifiedBy` | `@available` removed (inherited from parent) | ## Deprecations [#deprecations] No new deprecations. Existing deprecations unchanged: ```swift @available(*, deprecated, message: "Use .userPresence instead") case biometricOrPasscode @available(*, deprecated, message: "Use .biometryCurrentSet instead") case biometricOnly ``` ## Bug Fixes [#bug-fixes] * Fixed intermittent "unable to initialize storage" errors during app launch. The issue was caused by using `UserDefaults` for `keyId` storage, which does not guarantee persistence—particularly during iOS app prewarming when protected data may be unavailable. The SDK now stores the `keyId` in the Keychain and performs explicit availability checks before initialization, throwing a new `storageInitializedInBackground` error when the keychain is inaccessible. * Fixed a crash that could occur when `getCredentials()` or `updateMobileCredentialStatus()` were called concurrently from multiple threads. The crash was caused by unsafe concurrent access to internal state. The SDK now properly serializes access to shared resources during credential operations. ## Minimum Requirements [#minimum-requirements] * iOS 15+ for core SDK functionality * iOS 26+ for Digital Credentials API (DC API) features ## Dependencies [#dependencies] **Third party dependencies** * [CBORCoding](https://github.com/SomeRandomiOSDev/CBORCoding) (MIT license) * [swift-certificates](https://github.com/apple/swift-certificates.git) 1.7.0 (Apache-2.0 License) * [swift-asn1](https://github.com/apple/swift-asn1) 1.3.1 (Apache-2.0 License) **Apple frameworks** * [Security](https://developer.apple.com/documentation/security) * [CryptoKit](https://developer.apple.com/documentation/cryptokit/) * [LocalAuthentication](https://developer.apple.com/documentation/localauthentication) * [AuthenticationServices](https://developer.apple.com/documentation/authenticationservices) * [CoreBluetooth](https://developer.apple.com/documentation/corebluetooth) * [Combine](https://developer.apple.com/documentation/combine) * [OSLog](https://developer.apple.com/documentation/oslog) * [UIKit](https://developer.apple.com/documentation/uikit) * [AppKit (on macOS)](https://developer.apple.com/documentation/appkit) * [IdentityDocumentServices](https://developer.apple.com/documentation/IdentityDocumentServices) * [IdentityDocumentServicesUI](https://developer.apple.com/documentation/IdentityDocumentServicesUI) **Toolchain dependencies** * Swift 5.10. * iOS support: The SDK functionality is only available for devices from iOS 15 onwards. * Xcode: The SDK requires Xcode 26.0.0 (17A324) or later. Our CI builds use the latest stable Xcode available in GitHub Actions (setup-xcode action with the `latest-stable` label). Ensure that your development environment and CI pipelines meet or exceed this minimum to avoid build failures. * macOS 15 or later. ## Migration Steps [#migration-steps] ### Update `getCredential` calls [#update-getcredential-calls] The `skipStatusCheck` parameter has been renamed to `fetchUpdatedStatusList` with inverted semantics. When `fetchUpdatedStatusList` is `true` (default), the SDK will fetch the latest status list to ensure up-to-date revocation information. When `false`, it will skip fetching the status list. Update all calls accordingly: ```diff - let credential = try await holder.getCredential(credentialId: id, skipStatusCheck: true) + let credential = try await holder.getCredential(credentialId: id, fetchUpdatedStatusList: false) ``` | Old Parameter | New Parameter | Mapping | | ---------------------------------- | ---------------------------------------- | ------------------ | | `skipStatusCheck: false` (default) | `fetchUpdatedStatusList: true` (default) | No change needed | | `skipStatusCheck: true` | `fetchUpdatedStatusList: false` | Invert the boolean | Refer to [Revocation Status check](/docs/holding/credential-claiming-guides/revocation-status-check) for more information. ### Update `deinitialize` calls [#update-deinitialize-calls] The `deinitialize()` method is no longer `async`, so remove `await` from all call sites: ```diff - await MobileCredentialHolder.shared.deinitialize() + MobileCredentialHolder.shared.deinitialize() ``` ### Handle `OfferedCredential` changes [#handle-offeredcredential-changes] The `OfferedCredential` struct now includes a new required property `credentialConfigurationId: String`. If you are using the default decoding provided by the SDK, no changes are needed. However, if you have implemented a custom decoder for `OfferedCredential`, you must update it to handle the new field: ```diff struct OfferedCredential: Decodable { + let credentialConfigurationId: String let docType: String let claims: [Claim] let name: String? } ``` ### Handle `VerifierAuthenticationResult` changes [#handle-verifierauthenticationresult-changes] The `VerifierAuthenticationResult` enum now includes a new case `.unsigned(origin: String?)` to represent unsigned/unauthenticated requests. If you have any exhaustive `switch` statements on `VerifierAuthenticationResult`, you must add a new case to handle this scenario: ```diff switch verifierAuthenticationResult { case .authenticated(let verifierInfo): // Handle authenticated verifier case .failed(let error): // Handle authentication failure + case .unsigned(let origin): + // Handle unsigned/unauthenticated request (new in v5.0.0) + // origin contains the requesting website origin if available } ``` ### Enable Presenting Credentials via the Digital Credentials API (iOS 26+, optional) [#enable-presenting-credentials-via-the-digital-credentials-api-ios-26-optional] The iOS Holder SDK v5.0.0 introduces support for the Digital Credentials API, allowing credentials to be presented via an OS-native overlay without launching your app. To enable this feature, you need to initialize the SDK with a `DCConfiguration` that specifies your supported document types and app group. This is optional but recommended for a seamless user experience. ```swift // In main app initialization if #available(iOS 26.0, *) { let dcConfig = DCConfiguration( appGroup: "group.com.yourcompany.app", supportedDocTypes: [.mDL, .eudi] ) try MobileCredentialHolder.shared.initialize( instanceID: instanceID, dcConfiguration: dcConfig ) } else { try MobileCredentialHolder.shared.initialize(instanceID: instanceID) } ``` ### App Extension Migration (iOS 26+) [#app-extension-migration-ios-26] The methods marked as `@available(iOSApplicationExtension, unavailable)` are no longer accessible from app extensions. If you have an Identity Document Provider extension, you must migrate to using the new Digital Credentials APIs for handling credential requests. This involves initializing the SDK for extension use and creating presentation sessions from the system-provided context. ```swift // In your IdentityDocumentProviderExtension @available(iOS 26.0, *) func handleRequest(_ context: ISO18013MobileDocumentRequestContext) async throws { // Initialize for extension context try MobileCredentialHolder.shared.initializeAppExtension( appGroup: "group.com.yourcompany.app" ) // Create presentation session let session = try MobileCredentialHolder.shared.createDcPresentationSession(from: context) // Handle request... try await session.sendResponse(credentialIDs: selectedCredentialIDs) } ``` ### Update Toolchain [#update-toolchain] The SDK now requires Xcode 26.0.0 (17A324) or later. Ensure that your development environment and CI pipelines are updated to use Xcode 26.0.0+ to avoid build failures. # iOS Holder SDK v6.0.0 Migration Guide URL: /docs/holding/sdk-operations/migration-guides/ios-6.0.0-migration-guide Description: A comprehensive guide to migrating to iOS Holder SDK v6.0.0, covering breaking changes, new features, and step-by-step migration instructions. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in the iOS Holder SDK v6.0.0, including breaking changes, new features, and migration steps. This release focuses on strengthening trust at issuance, improving predictability in credential handling, and increasing consistency across platforms. It introduces optional SDK Tethering to connect the SDK to your MATTR VII tenant and optional Wallet Attestation support, enabling issuers to verify wallet integrity before issuing credentials. Together, these changes make holder-side integrations more reliable and easier to reason about in production environments. SDK Tethering (and Wallet Attestation, which builds on it) is **optional** in this release. You opt in by passing a `platformConfiguration` when initializing the SDK. If you omit it, the SDK skips registration, tethering, and wallet attestation, and existing integrations continue to work. We expect to make SDK Tethering required in an upcoming release, so we recommend adopting it now to prepare. ## Key Features [#key-features] * **SDK Tethering (optional)**: The iOS Holder SDK can now be tethered to a MATTR VII tenant, tying each SDK/app instance to your tenant. This allows you to view details about registered and active app instances directly from your tenant for operational insights. SDK Tethering also establishes a remote management channel that we expect to extend in the future, such as remote syncing of trusted issuer lists and eventing. Tethering is enabled by providing a `platformConfiguration` at initialization. * **Wallet Attestation support (optional)**: Building on the SDK Tethering channel, the iOS Holder SDK now supports Wallet Attestation, allowing your holder application to prove to issuers that it is a trusted wallet before credentials are issued. When an issuer requires it, the SDK uses the tethering connection to obtain wallet attestation tokens from your MATTR VII tenant. Wallet Attestation requires SDK Tethering to be configured. * **Stronger application identity during issuance**: Pre-authorized credential issuance flows now pass the application's `client_id`, ensuring the holder is accurately represented when interacting with issuers. This improves compatibility with issuers applying stricter controls. * **More predictable credential retrieval results**: Credential retrieval responses are now more structured and deterministic, explicitly identifying success or failure with guaranteed fields per result type. * **Cross-platform alignment**: Naming and response structures have been aligned with the Android Holder SDK, minimizing divergence for teams maintaining cross-platform applications. * **Improved biometric and storage lifecycle handling**: Edge cases in biometric authentication, storage lifecycle, and app lifecycle events (such as prewarming or reinstall scenarios) have been addressed. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency, and improve overall reliability. ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v6.0.0 that require updates to your existing implementation: | # | Change | Impact | | -- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 1 | `initialize` and `initializeAppExtension` are now asynchronous | Add `await` to all call sites and call these methods from an asynchronous context. | | 2 | Pre-authorized code flow now uses the application's `client_id` instead of a default identifier | Ensure your application has a valid configured `client_id` and issuers recognize it. | | 3 | Credential retrieval result shape updated to explicitly identify success or failure with guaranteed fields | Update result parsing logic to use success/failure branching. | | 4 | `MobileCredentialAuthenticationOption` renamed to `DeviceAuthenticationOption` (and the `mobileCredentialAuthenticationOption:` parameter renamed to `deviceAuthenticationOption:`) | Update all imports, type references, and session-creation parameter names. | | 5 | `OnlinePresentationSession.matchedCredentials` is now the `getMatchedCredentials()` method and returns a non-optional array | Replace property access with the method call and remove optional handling. | | 6 | `VerificationResult` renamed to `MobileCredentialVerificationResult`, with `.reason` renamed to `.failureType` | Update all type references, rename `.reason` to `.failureType`, and remove use of `VerificationFailedReason`. | | 7 | Wallet Attestation introduces new error cases on `MobileCredentialHolderError` and `RetrieveCredentialErrorType` | Update error handling, logging, analytics, and support diagnostics to account for the new wallet attestation error cases. | | 8 | `challenge` in `requestMobileCredentials` is now optional to align with Android | Remove assumptions that `challenge` must always be supplied. | | 9 | `MobileCredential.claims` and `MobileCredentialMetadata.claims` are now non-optional, and credentials with empty claims are rejected | Remove optional chaining on `claims` and handle the decoding error where credentials are retrieved or added. | | 10 | `MobileCredentialResponse.credentials` and `MobileCredentialResponse.credentialErrors` are now non-optional arrays | Remove optional handling on these properties. | | 11 | `docType` is now serialized under the JSON key `docType` (previously `doctype`) | Update any persisted or parsed serialized forms to use `docType`. | | 12 | A `.none` case was added to `UserAuthenticationType`, and device-key auth types are now non-optional | Pass `DeviceKeyAuthenticationPolicy(type: .none)` instead of `nil` to disable authentication for a device key. | | 13 | The `claims` property on `OfferedCredential` is now optional (`[Claim]?`), aligning with the OID4VCI 1.0 specification | Handle the case where `OfferedCredential.claims` is `nil`, indicating the offer contains no claim data. | | 14 | New required `tokenEndpointAuthMethodsSupported`, `authorizationServerIssuer`, and `nonceEndpoint` properties added to `DiscoveredCredentialOffer` | Supply the new arguments if you construct this type directly, and update any stored representations. | | 15 | `MobileCredentialVerificationFailureType` and `TrustedCertificateVerificationFailureType` now serialize as a `{type, message}` object instead of a plain raw-value string | Update any storage or transport layer that persists or forwards these serialized values. | | 16 | Remote mobile (app-to-app) verification now maps the backend `IssuerNotTrusted` failure to `MobileCredentialVerificationFailureType.TrustedIssuerCertificateNotFound` | Remove any `IssuerNotTrusted` handling and handle `TrustedIssuerCertificateNotFound` instead. | | 17 | The internal storage location for app extension logs has changed | No code change required (the same `getCurrentLogFilePath(appGroup:)` method is used), but app extension logs written before the upgrade become inaccessible. | SDK initialization does **not** require platform/tenant configuration in this release. The `platformConfiguration` parameter is optional. Provide it only if you want to enable SDK Tethering and Wallet Attestation. See [Update SDK initialization](#update-sdk-initialization). ## Migration Steps [#migration-steps] ### Make `initialize` calls asynchronous [#make-initialize-calls-asynchronous] The `initialize` and `initializeAppExtension` methods are now asynchronous. Add `await` to all call sites and call them from an asynchronous context: ```diff - try MobileCredentialHolder.shared.initialize(instanceID: instanceID) + try await MobileCredentialHolder.shared.initialize(instanceID: instanceID) ``` ### (Optional) Create a holder application on your MATTR VII tenant [#optional-create-a-holder-application-on-your-mattr-vii-tenant] SDK Tethering is **optional** in this release. If you do not need it, you can skip this step and the next one. The SDK continues to function without a `platformConfiguration`. To enable SDK Tethering, the iOS Holder SDK connects to a MATTR VII tenant. Tethering gives you access to a centralized view of registered and active app instances in MATTR VII, the optional Wallet Attestation feature, and other centralized management capabilities we plan to deliver over this channel. We expect SDK Tethering to become required in an upcoming release. We recommend adopting it now to prepare, even if you do not yet use Wallet Attestation. To register your iOS application, make a request to create a holder application: ```http title="Request" POST /v1/holder/applications ``` ```json title="Request body" { "name": "My iOS Holder Application", "clientId": "your-wallet-client-id", "type": "ios", "bundleId": "com.yourcompany.holderapp", "teamId": "YOUR_APPLE_TEAM_ID" } ``` * `name`: A unique name to identify your holder application. * `clientId`: The OAuth 2.0 `client_id` that identifies your wallet application. This value is included in attestation JWTs and must match the `client_id` configured on the issuer's Authorization Server. * `type`: Must be `ios` for an iOS application. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID. Once created, your holder application will be able to use the SDK and interact with the MATTR VII platform (for example, to obtain attestation tokens). The response will include a unique `id` for your application, which must be used when initializing the SDK so that it can correctly identify and authenticate your application. The `clientId` you configure here is the same value you must: 1. Pass as the `clientId` parameter when calling [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/retrievecredentials\(credentialoffer:clientid:transactioncode:devicekeyauthenticationpolicy:\)). 2. Register with each issuer you intend to interact with, so the issuer can identify and trust requests coming from your wallet application. ### (Optional) Enable tethering at SDK initialization [#optional-enable-tethering-at-sdk-initialization] To enable SDK Tethering, pass a `platformConfiguration` when initializing the SDK. This parameter is optional. Omit it to initialize the SDK without tethering: ```diff - try await MobileCredentialHolder.shared.initialize(instanceID: instanceID) + let platformConfig = PlatformConfiguration( + tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, + applicationId: "1ef1f867-20b4-48ea-aec1-bea7aff4964c" + ) + try await MobileCredentialHolder.shared.initialize( + instanceID: instanceID, + platformConfiguration: platformConfig + ) ``` * `tenantHost`: The URL of your MATTR VII tenant. This must be the tenant where your holder application is configured. * `applicationId`: The `id` of your configured MATTR VII holder application. When `platformConfiguration` is omitted, the SDK skips registration and tethering, and wallet attestation is not available. ### Handle new error cases [#handle-new-error-cases] This release adds new cases to `MobileCredentialHolderError`, which is a **breaking change** that requires updates to exhaustive `switch` statements regardless of whether you enable SDK Tethering: * `invalidLicense`: Thrown when the SDK license is invalid or expired. * `failedToRegister`: Thrown when app registration with the MATTR VII tenant fails. * `invalidWalletAttestation`: Thrown when the authorization server rejects the wallet attestation during credential retrieval. * `calledFromAppExtension`: Thrown when an SDK API that is unavailable in app extensions is called from an app extension at runtime. If you enable SDK Tethering and Wallet Attestation, the following per-credential failure types are also added to `RetrieveCredentialErrorType`: * `walletAttestationFailed`: Returned when wallet attestation generation fails. * `invalidWalletAttestation`: Returned when the attestation is rejected by the credential endpoint. The [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/retrievecredentials\(credentialoffer:clientid:transactioncode:devicekeyauthenticationpolicy:\)) method may now throw `invalidWalletAttestation` and return the new per-credential failure types. Wallet attestation errors only occur when the SDK is tethered and an issuer requires attestation. Update your error handling, logging, analytics, and support diagnostics to account for these new error cases. Ensure any SDK API calls from app extensions are wrapped in appropriate availability checks to prevent runtime errors. ### Update `client_id` configuration [#update-client_id-configuration] Previously, the `client_id` passed to [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/mobilecredentialholder/retrievecredentials\(credentialoffer:clientid:transactioncode:devicekeyauthenticationpolicy:\)) was not shared with the issuer during the pre-authorized code flow, so any value would work. This is no longer the case. The SDK now presents the `client_id` to the issuer as part of wallet attestation, and the issuer validates it against its list of trusted wallet providers. To prepare for this change: 1. **Coordinate with the issuer** to register your wallet application as a trusted wallet provider. The issuer will provide you with a `client_id` that identifies your application. 2. **Pass the issuer-provided `client_id`** when calling `retrieveCredentials`. ```diff let results = try await holder.retrieveCredentials( credentialOffer: offer, - clientId: "any-value", + clientId: "issuer-provided-client-id" ) ``` Issuance flows that previously worked with an arbitrary `client_id` will fail if the issuer requires a trusted wallet provider. Ensure you have coordinated with each issuer and obtained the correct `client_id` before upgrading. Test direct issuance flows to confirm credentials are issued successfully. ### Update credential retrieval result handling [#update-credential-retrieval-result-handling] `RetrieveCredentialResult` has been converted from a struct with optional fields to an enum with explicit `.success` and `.failure` cases. Each case carries guaranteed associated values, removing ambiguity from result handling. The `retrieveCredentials` function returns `[RetrieveCredentialResult]`, an array with one result per offered credential. Update your iteration logic to use pattern matching: ```diff let results = try await holder.retrieveCredentials(options) for result in results { - if let credentialId = result.credentialId { - // Use result.docType and credentialId - } else if let error = result.error { - // Use result.docType and error - } + switch result { + case .success(let docType, let credentialId): + // docType and credentialId are guaranteed + case .failure(let docType, let error): + // docType and error are guaranteed + } } ``` Update tests and any downstream logic that checked for `nil` fields. ### Rename `MobileCredentialAuthenticationOption` to `DeviceAuthenticationOption` [#rename-mobilecredentialauthenticationoption-to-deviceauthenticationoption] Update all imports, type references, and configuration objects: ```diff - let authOption: MobileCredentialAuthenticationOption = .biometryCurrentSet + let authOption: DeviceAuthenticationOption = .biometryCurrentSet ``` ### Update `matchedCredentials` handling for online presentation [#update-matchedcredentials-handling-for-online-presentation] The optional `matchedCredentials` property on `OnlinePresentationSession` has been replaced by a `getMatchedCredentials()` method that returns a non-optional array. Replace property access (and any optional handling) with the method call: ```diff - if let matched = session.matchedCredentials { - // Handle matched credentials - } else { - // Handle nil case - } + // getMatchedCredentials() always returns an array (may be empty) + let matched = session.getMatchedCredentials() + if matched.isEmpty { + // Handle no matched credentials + } else { + // Handle matched credentials + } ``` ### Update `VerificationResult` to `MobileCredentialVerificationResult` [#update-verificationresult-to-mobilecredentialverificationresult] The `VerificationResult` type has been renamed to `MobileCredentialVerificationResult` and aligned structurally with Android. Its `reason` property has been renamed to `failureType`, typed directly as `MobileCredentialVerificationFailureType?` rather than the now-removed `VerificationFailedReason` wrapper: ```diff - let result: VerificationResult = ... + let result: MobileCredentialVerificationResult = ... - let failure = result.reason + let failure = result.failureType ``` Replace all references to `VerificationResult` with `MobileCredentialVerificationResult`, rename `.reason` to `.failureType`, and remove any usage of `VerificationFailedReason`. The same `.reason` → `.failureType` rename applies to `TrustedCertificateVerificationResult`. You will also need to validate cross-platform result handling and update any downstream mapping logic. ### Handle optional `challenge` in `requestMobileCredentials` [#handle-optional-challenge-in-requestmobilecredentials] The `challenge` parameter is now optional in `requestMobileCredentials`. Review call sites and validation logic, and remove any app-side assumptions that `challenge` must always be supplied: ```diff - // challenge was previously required - let request = try holder.requestMobileCredentials(challenge: challenge, ...) + // challenge is now optional + let request = try holder.requestMobileCredentials(challenge: challenge, ...) // still works + let request = try holder.requestMobileCredentials(...) // also valid without challenge ``` ### Remove optional handling on `claims` [#remove-optional-handling-on-claims] `MobileCredential.claims` and `MobileCredentialMetadata.claims` are now non-optional (`[NameSpace: [ElementID: ElementValue]]`), defaulting to `[:]` when namespaces are absent. Remove optional chaining on these accesses: ```diff - let value = credential.claims?["org.iso.18013.5.1"]?["family_name"] + let value = credential.claims["org.iso.18013.5.1"]?["family_name"] ``` Retrieving or decoding a credential whose `IssuerSigned` data contains no namespaces (or a namespace with no claims) now fails with a decoding error instead of producing a credential with empty claims. Handle this decoding error where credentials are retrieved or added. ### Remove optional handling on `MobileCredentialResponse` collections [#remove-optional-handling-on-mobilecredentialresponse-collections] `MobileCredentialResponse.credentials` and `MobileCredentialResponse.credentialErrors` are now non-optional arrays that default to empty, instead of optional arrays that could be `nil`. Remove optional handling on these properties: ```diff - for credential in response.credentials ?? [] { ... } + for credential in response.credentials { ... } ``` ### Update `docType` serialization [#update-doctype-serialization] The document type in `RetrieveCredentialResult` and `OfferedCredential` is now encoded under the JSON key `docType` instead of `doctype`. Update any code that persists or parses these serialized forms to use `docType`. ### Update `UserAuthenticationType` handling [#update-userauthenticationtype-handling] A `.none` case was added to `UserAuthenticationType` to explicitly disable user authentication. Consequently, `DeviceKeyAuthenticationInfo.type` and `DeviceKeyAuthenticationPolicy.type` are now non-optional `UserAuthenticationType`, and `DeviceKeyAuthenticationPolicy(type:)` no longer accepts `nil`. To disable authentication for a specific device key, pass `.none` instead of `nil`: ```diff - let policy = DeviceKeyAuthenticationPolicy(type: nil) + let policy = DeviceKeyAuthenticationPolicy(type: .none) ``` ### Handle optional `OfferedCredential.claims` [#handle-optional-offeredcredentialclaims] The `claims` property on [`OfferedCredential`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/offeredcredential) is now optional (`[Claim]?`), aligning with the [OID4VCI 1.0 specification](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-12.2.4). It is only populated for offers that contain claim data. Handle the case where `claims` is `nil`: ```diff - let claims = offeredCredential.claims + let claims = offeredCredential.claims // may be nil when the offer contains no claim data + if let claims { + // Use the claims + } ``` ### Supply new `DiscoveredCredentialOffer` properties [#supply-new-discoveredcredentialoffer-properties] The required `tokenEndpointAuthMethodsSupported`, `authorizationServerIssuer`, and `nonceEndpoint` properties have been added to [`DiscoveredCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-ios/latest/documentation/mobilecredentialholdersdk/discoveredcredentialoffer): * `tokenEndpointAuthMethodsSupported`: An array of authentication methods supported by the token endpoint. * `authorizationServerIssuer`: The issuer identifier of the authorization server. * `nonceEndpoint`: The authorization server nonce endpoint used for OID4VCI proof-of-possession. The public initializer now requires these arguments and the encoded representation includes the corresponding fields. Most callers receive this type from `discoverCredentialOffer` and are not affected. If you construct or decode `DiscoveredCredentialOffer` directly, update your call sites and any stored representations. ### Update failure-type serialization handling [#update-failure-type-serialization-handling] `MobileCredentialVerificationFailureType` and `TrustedCertificateVerificationFailureType` now encode and decode as a `{type, message}` object instead of a plain raw-value string: ```diff - "TrustedIssuerCertificateNotFound" + {"type": "TrustedIssuerCertificateNotFound", "message": "Trusted issuer certificate not found"} ``` If you persist or forward the serialized value of either failure type, update your storage or transport layer to produce and consume the new format. ### Update issuer-trust failure handling in remote mobile verification [#update-issuer-trust-failure-handling-in-remote-mobile-verification] During remote mobile (app-to-app) verification, a backend `IssuerNotTrusted` failure reason is now decoded as `MobileCredentialVerificationFailureType.TrustedIssuerCertificateNotFound` instead of being surfaced as `IssuerNotTrusted` via the now-removed `VerificationFailedReason` wrapper. Remove any `IssuerNotTrusted` handling and update it to handle `TrustedIssuerCertificateNotFound` as the failure type for issuer trust failures: ```diff - case .issuerNotTrusted: + case .trustedIssuerCertificateNotFound: // Handle issuer trust failure ``` ### Note the app extension log location change [#note-the-app-extension-log-location-change] The internal storage location for app extension logs has changed. Logs are still retrieved with the same `getCurrentLogFilePath(appGroup:)` method, so **no code change is required**. However, any app extension logs written before upgrading (up to the 48-hour retention window) will no longer be accessible after the upgrade. The location for main SDK logs remains unchanged. # React Native Holder SDK v10.0.0 Migration Guide URL: /docs/holding/sdk-operations/migration-guides/react-native-10.0.0-migration-guide Description: A comprehensive guide to migrating to React Native Holder SDK v10.0.0, covering breaking changes, new features, and step-by-step migration instructions. This is a **draft** migration guide, shared ahead of general availability to help you gauge migration effort and plan ahead of the release. We will update this page to a final copy when the SDK is generally available. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in React Native Holder SDK v10.0.0, including breaking changes, new features, and migration steps. This release focuses on strengthening trust at issuance, improving predictability in credential handling, and increasing consistency across platforms. It introduces optional SDK Tethering to connect the SDK to your MATTR VII tenant, adds optional Wallet Attestation support to enable issuers to verify wallet integrity, and adds support in the JavaScript/TypeScript layer to bridge the new platform configuration. SDK Tethering (and Wallet Attestation, which builds on it) is **optional** in this release. You opt in by passing a `platformConfiguration` when initializing the SDK. If you omit it, the SDK skips registration, tethering, and wallet attestation, and existing integrations continue to work. We expect to make SDK Tethering required in an upcoming release, so we recommend adopting it now to prepare. ## Key Features [#key-features] * **SDK Tethering (optional)**: The React Native Holder SDK can now be tethered to a MATTR VII tenant, tying each SDK/app instance to your tenant. This allows you to view details about registered and active app instances directly from your tenant for operational insights. SDK Tethering also establishes a remote management channel that we expect to expand soon with additional features, such as remote syncing of trusted issuer lists and eventing. Tethering is enabled by providing a `platformConfiguration` at initialization. * **Wallet Attestation support (optional)**: Building on the SDK Tethering channel, the React Native Holder SDK now supports Wallet Attestation, allowing your holder application to prove to issuers that it is a trusted wallet before credentials are issued. When an issuer requires it, the SDK uses the tethering connection to obtain wallet attestation tokens from your MATTR VII tenant. Wallet Attestation requires SDK Tethering to be configured. * **Stronger application identity during issuance**: Pre-authorized credential issuance flows now pass the application's `client_id`, ensuring the holder is accurately represented when interacting with issuers. This improves compatibility with issuers applying stricter controls. * **More predictable credential retrieval results**: Credential retrieval responses are now more structured and deterministic, explicitly identifying success or failure with guaranteed fields per result type. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency, and improve overall reliability. ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v10.0.0 that require updates to your existing implementation: | # | Change | Impact | | - | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | 1 | Pre-authorized code flow now uses the application's `client_id` instead of a default identifier | Ensure your application has a valid configured `client_id` and issuers recognize it. | | 2 | Credential retrieval result shape updated to explicitly identify success or failure with guaranteed fields | Update result parsing and handling logic. | | 3 | `doctype` renamed to `docType` | Rename all usages of `doctype` to `docType`. | | 4 | `MobileCredentialAuthenticationOption` renamed to `DeviceAuthenticationOption` | Update imports, type references, and configuration objects. | SDK initialization does **not** require platform/tenant configuration in this release. The `platformConfiguration` option is optional — provide it only if you want to enable SDK Tethering and Wallet Attestation. See [Enable tethering at SDK initialization](#optional-enable-tethering-at-sdk-initialization). ## Migration Steps [#migration-steps] ### (Optional) Create holder applications on your MATTR VII tenant [#optional-create-holder-applications-on-your-mattr-vii-tenant] SDK Tethering is **optional** in this release. If you do not need it, you can skip this step and the next one — the SDK continues to function without a `platformConfiguration`. To enable SDK Tethering, the React Native Holder SDK connects to a MATTR VII tenant via the underlying native SDKs. Tethering gives you access to a centralized view of registered and active app instances in MATTR VII, the optional Wallet Attestation feature, and other centralized management capabilities we plan to deliver over this channel. We expect SDK Tethering to become required in an upcoming release. We recommend adopting it now to prepare, even if you do not yet use Wallet Attestation. Since React Native targets both iOS and Android, you need to create **two** holder applications, one for each platform. #### Create the iOS holder application [#create-the-ios-holder-application] Make a request to create a holder application: ```http title="Request" POST /v1/holder/applications ``` ```json title="Request body" { "name": "My RN Holder Application (iOS)", "clientId": "your-wallet-client-id", "type": "ios", "bundleId": "com.yourcompany.holderapp", "teamId": "YOUR_APPLE_TEAM_ID" } ``` * `name`: A unique name to identify this holder application. * `clientId`: The OAuth 2.0 `client_id` that identifies your wallet application. This value is included in attestation JWTs and must match the `client_id` configured on the issuer's Authorization Server. * `type`: Must be `ios` for the iOS target. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID. The response will include a unique `id` for your application, which must be used when initializing the SDK so that it can correctly identify and authenticate your application. #### Create the Android holder application [#create-the-android-holder-application] ```http title="Request" POST /v1/holder/applications ``` ```json title="Request body" { "name": "My RN Holder Application (Android)", "clientId": "your-wallet-client-id", "type": "android", "packageName": "com.yourcompany.holderapp", "packageSigningCertificateThumbprints": [ "1232584b6f6a892d356899fb9576c5f226a179e6199f2b7a1d837b5c234c5a8e" ] } ``` * `name`: A unique name to identify this holder application. * `clientId`: The OAuth 2.0 `client_id` that identifies your wallet application. This value is included in attestation JWTs and must match the `client_id` configured on the issuer's Authorization Server. * `type`: Must be `android` for the Android target. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/holding/android-app-signing) for more information. The response will include a unique `id` for your application, which must be used when initializing the SDK so that it can correctly identify and authenticate your application. Once both application configurations are created, your application will be able to use the SDK and interact with the MATTR VII platform (for example, to obtain attestation tokens). The `clientId` you configure here is the same value you must: 1. Pass as the `clientId` in the options when calling [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/retrieveCredentials.html). 2. Register with each issuer you intend to interact with, so the issuer can identify and trust requests coming from your wallet application. ### (Optional) Enable tethering at SDK initialization [#optional-enable-tethering-at-sdk-initialization] To enable SDK Tethering, pass a `platformConfiguration` through the JS/TS layer into the native SDKs when initializing. This option is optional — omit it to initialize the SDK without tethering. Since React Native bridges both iOS and Android, and each platform has its own holder application registered on your MATTR VII tenant (see [the previous step](#optional-create-holder-applications-on-your-mattr-vii-tenant)), your initialization code must pass the correct platform-specific `applicationId` at runtime. Use `Platform.OS` to select the appropriate value: ```diff import { initialize } from "@mattrglobal/mobile-credential-holder-react-native"; + import { Platform } from "react-native"; - await initialize({ instanceId: "your-instance-id" }); + const applicationId = + Platform.OS === "android" + ? "your-android-holder-application-id" + : "your-ios-holder-application-id"; + + await initialize({ + instanceId: "your-instance-id", + platformConfiguration: { + tenantHost: "https://your-tenant.vii.mattr.global", + applicationId, + }, + }); ``` Replace the placeholder values with the `id` returned when you created each holder application. In practice, you would typically store these values in a configuration file or environment variables. When `platformConfiguration` is omitted, the SDK skips registration, tethering, and wallet attestation. Confirm that: * The `applicationId` used on iOS corresponds to the holder application created with your app's `bundleId` and `teamId`. * The `applicationId` used on Android corresponds to the holder application created with your app's `packageName` and signing certificate thumbprints. * Environment-specific tenant URLs are correctly configured. ### Handle new Wallet Attestation error modes [#handle-new-wallet-attestation-error-modes] If you enable SDK Tethering and Wallet Attestation, new error scenarios can occur during credential issuance. These errors only arise when the SDK is tethered and an issuer requires attestation, specifically when: * The SDK requests an attestation token from your MATTR VII tenant. * The SDK presents wallet attestation credentials to an issuer's Authorization Server at its `/token` endpoint. * The SDK requests credentials from an issuer's `/credential` endpoint with DPoP-bound access tokens. The following methods are affected: * [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/retrieveCredentials.html) — may return new error types in both the thrown error and per-credential results. New error types will be added to `MobileCredentialHolderError` and `RetrieveCredentialError` (a **breaking change** requiring updates to error type handling). Update your error handling, logging, analytics, and support diagnostics to account for new wallet attestation error cases. An exhaustive list of new errors will be provided with the final migration guide. ### Update `client_id` configuration [#update-client_id-configuration] Previously, the `client_id` passed to [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/retrieveCredentials.html) was not shared with the issuer during the pre-authorized code flow, so any value would work. This is no longer the case — the SDK now presents the `client_id` to the issuer as part of wallet attestation, and the issuer validates it against its list of trusted wallet providers. To prepare for this change: 1. **Coordinate with the issuer** to register your wallet application as a trusted wallet provider. The issuer will provide you with a `client_id` that identifies your application. 2. **Pass the issuer-provided `client_id`** in the options when calling `retrieveCredentials`. ```diff const result = await retrieveCredentials({ credentialOffer: offer, - clientId: "any-value", + clientId: "issuer-provided-client-id" }); ``` Issuance flows that previously worked with an arbitrary `client_id` will fail if the issuer requires a trusted wallet provider. Ensure you have coordinated with each issuer and obtained the correct `client_id` before upgrading. Test direct issuance flows to confirm credentials are issued successfully. ### Update credential retrieval result handling [#update-credential-retrieval-result-handling] `RetrieveCredentialsResponse` has changed from an array of flat objects with optional fields to an array of `RetrieveCredentialItem` — a discriminated union with `isSuccess` as the discriminator. Each item is either a `RetrieveCredentialSuccess` (with guaranteed `docType` and `credentialId`) or a `RetrieveCredentialFailure` (with guaranteed `docType` and `error`). Update your result iteration logic to narrow on `isSuccess`: ```diff const result = await retrieveCredentials(options); if (result.isOk()) { for (const item of result.value) { - if (item.credentialId) { - // Use item.doctype and item.credentialId + if (item.isSuccess) { + // item.docType and item.credentialId are guaranteed } else { - // Use item.doctype and item.error + // item.docType and item.error are guaranteed } } } ``` Update TypeScript type guards, tests, and any downstream logic that checked for optional fields. ### `doctype` renamed to `docType` [#doctype-renamed-to-doctype] The `doctype` field has been renamed to `docType` (camelCase). This affects: * [`RetrieveCredentialsResponse`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/RetrieveCredentialsResponse.html) returned by [`retrieveCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/retrieveCredentials.html) * [`OfferedCredential`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/types/OfferedCredential.html) returned by [`discoverCredentialOffer`](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/functions/discoverCredentialOffer.html) Update all references from `.doctype` to `.docType`. ```diff - const documentType = credential.doctype; + const documentType = credential.docType; ``` ### Rename `MobileCredentialAuthenticationOption` to `DeviceAuthenticationOption` [#rename-mobilecredentialauthenticationoption-to-deviceauthenticationoption] Update all TypeScript imports, type references, and configuration objects: ```diff - import { MobileCredentialAuthenticationOption } from "@mattrglobal/mobile-credential-holder-react-native"; + import { DeviceAuthenticationOption } from "@mattrglobal/mobile-credential-holder-react-native"; - const authOption: MobileCredentialAuthenticationOption = ...; + const authOption: DeviceAuthenticationOption = ...; ``` # React Native Holder SDK v9.0.0 Migration Guide URL: /docs/holding/sdk-operations/migration-guides/react-native-9.0.0-migration-guide Description: A comprehensive guide to migrating to React Native Holder SDK v9.0.0, covering breaking changes, new features, and step-by-step migration instructions. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in React Native Holder SDK v9.0.0, including breaking changes, new features, and migration steps. ## Key Features [#key-features] * **Seamless, OS-native credential presentation (DC API support)**: The React Native Holder SDK now integrates with the Digital Credentials API on supported platforms (Android devices running Google Play services version 24.0+ and devices running iOS 26 and later), allowing credentials to be presented via an OS overlay without launching your holder app. The OS automatically surfaces only matching credentials across installed holders, reducing friction, avoiding dead ends, and significantly improving the user experience. * **Device key authentication support**: The React Native Holder SDK now supports defining a specific authentication method for claiming and accessing specific credentials. This allows you to control how users authenticate. For example, requiring Face ID, fingerprint, or a device passcode, depending on the sensitivity of each credential. * **Verifier Authentication**: The Holder SDK now supports Verifier Authentication as defined in ISO/IEC 18013-5:2021. This allows a Holder app to confirm the identity of a verifier when it receives a credential request. This helps improve privacy and trust by allowing you to inform users (or prevent sharing altogether) when a credential is requested by a verifier outside your trusted network. Support for this feature requires implementation in both the Holder and Verifier apps. * **NFC Support (Android Only)**: The React Native Holder SDK now supports using NFC to start a verification or presentation session on Android devices. This allows users to begin a proximity session by simply tapping two NFC-enabled devices together instead of scanning a QR code. * **Status Lists Draft 14 Support**: The SDK now supports the Token Status List Draft 14 specification while maintaining existing support for Draft 3. * **Clearer OID4VCI interoperability (v1.0 alignment)**: The React Native Holder SDK now aligns with the finalized OID4VCI v1.0 specification (upgraded from draft-12). This alignment improves interoperability across the ecosystem, enabling other systems to clearly identify your supported version and feature set, ensuring smoother integrations and consistent behavior across platforms. * **Stronger cryptography and standards alignment**: Updated COSE algorithms (as per RFC 9864) strengthen cryptographic compatibility and ensure continued compliance with evolving standards. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency across mobile environments, and improve overall user experience. For a detailed list of changes included in this release, refer to the [SDK Changelog](https://api-reference-sdk.mattr.global/mobile-credential-holder-react-native/latest/index.html#md:change-log). ## Breaking Changes [#breaking-changes] | # | Element | Change | Impact | | - | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | 1 | `getCredential(credentialId, { skipStatusCheck = ... })` | Parameter renamed to `fetchUpdatedStatusList = ...` with **inverted semantics** | All call sites using `skipStatusCheck = ...` must be updated. | | 2 | `initialize(...)` | Can return three new error types: `StorageInitializedInBackground`, `SdkInitialized`, `InvalidInstanceID` | Exhaustive error handling must be updated. | | 3 | `ProximityPresentationSessionTerminationErrorType` | New `Exception` value added | Exhaustive switches must handle new case. | | 4 | WebCallbackActivity Android Manifest entry | Activity class path moved from global.mattr.mobilecredential.common.webcallback.WebCallbackActivity to global.mattr.mobilecredential.holder.webcallback.WebCallbackActivity | Relevant only if you use web callbacks in the Holder SDK. Update the Android Manifest entry accordingly. | | 5 | Xcode / Toolchain | Underlying iOS SDK built with Xcode 26.0.0 | Requires Xcode 26.0.0+. Builds will fail on earlier toolchains (e.g. Xcode 16.4). CI environments must be upgraded. | ## New Additions [#new-additions] ### Functions [#functions] | Function | Description | Platform | | --------------------------------------- | ---------------------------------------------- | ------------ | | `getCurrentLogFilePath(options?)` | Returns SDK log file path | All | | `setDeviceEngagementListener(listener)` | Sets NFC device engagement listener | Android only | | `removeDeviceEngagementListener()` | Removes NFC device engagement listener | Android only | | `getNfcConfiguration()` | Returns persisted or default NFC configuration | Android only | | `setNfcConfiguration(config)` | Persists new NFC configuration | Android only | ### Updated Function Signatures [#updated-function-signatures] * `generateDeviceKey(options?)` — new optional `authenticationPolicy?: DeviceKeyAuthenticationPolicy` * `retrieveCredentials(options)` — new optional `authenticationPolicy?: DeviceKeyAuthenticationPolicy` * `retrieveCredentialsUsingAuthorizationSession(options)` — new optional `authenticationPolicy?: DeviceKeyAuthenticationPolicy` * `createProximityPresentationSession(options)` — new optional `engagementFromNfc?: boolean` ### New initialize Options [#new-initialize-options] * `loggerConfiguration?: LoggerConfiguration` — configure SDK logging: logLevel, callbackLogLevel, logDir, callback on log events. * `dcConfiguration?: DCConfiguration` — enables Digital Credentials API support: * iOS: `DCConfigurationIOS` (appGroup, supportedDocTypes) * Android: `DCConfigurationAndroid` (enabled: boolean) ### New Types & Enums [#new-types--enums] * Logging: * `LogLevel` * `LoggerConfiguration` * NFC: * `NfcConfiguration` * `NfcHandoverMode` * `NfcError` * NFC Listener type * Digital Credentials API: * `DCConfiguration` * `DCConfigurationIOS` * `DCConfigurationAndroid` * `SupportedDocType` * Device Key Authentication: * `DeviceKeyAuthenticationPolicy` * `DeviceKeyAuthenticationType` * `DeviceKeyAuthenticationInfo` * `DeprecatedDeviceKeyAuthenticationType` * Verifier Authentication: * `VerifierAuthenticationResult` * `VerifierAuthenticationError` * `VerifierAuthenticationErrorType` * `VerifierInfo` ### New Error Values [#new-error-values] | Location | New value | | -------------------------------------------------- | ----------------------------------------------------------------------- | | `MobileCredentialHolderErrorType` | `StorageInitializedInBackground`, `SdkInitialized`, `InvalidInstanceID` | | `RetrieveCredentialsErrorTypes` | `UnsupportedDeviceKeyAuthenticationPolicy` | | `ProximityPresentationSessionTerminationErrorType` | `Exception` | ## Minimum Requirements [#minimum-requirements] **iOS** * iOS 15+ for core SDK functionality * iOS 26+ for Digital Credentials API (DC API) features **Android** * Android 7 / Nougat / API 24. * The underlying Android Holder SDK is built using Kotlin 2.0. This adds some intrinsic dependencies into your build tools. * Kotlin 2.0 is supported from AGP version [8.5](https://developer.android.com/build/kotlin-support). * AGP 8.5 is supported from Gradle version [8.7](https://developer.android.com/build/releases/about-agp) and Android Studio Koala [2024.1.1](https://developer.android.com/studio/releases). ## Migration Steps [#migration-steps] ### Update `getCredential` calls [#update-getcredential-calls] The `skipStatusCheck` parameter has been renamed to `fetchUpdatedStatusList` with inverted semantics. When `fetchUpdatedStatusList` is `true` (default), the SDK will fetch the latest status list to ensure up-to-date revocation information. When `false`, it will skip fetching the status list and rely on cached data (when valid). Update all calls accordingly: ```diff - const credential = await getCredential(id, { skipStatusCheck: true }); + const credential = await getCredential(id, { fetchUpdatedStatusList: false }); ``` | Old Parameter | New Parameter | | ----------------------------------- | ----------------------------------------- | | `skipStatusCheck = false` (default) | `fetchUpdatedStatusList = true` (default) | | `skipStatusCheck = true` | `fetchUpdatedStatusList = false` | Refer to [Revocation Status check](/docs/holding/credential-claiming-guides/revocation-status-check) for more information. ### Update Error Handling for initialize() [#update-error-handling-for-initialize] If your code exhaustively handles errors from `initialize()`, add support for the new error types: * `StorageInitializedInBackground` * `SdkInitialized` * `InvalidInstanceID` ### Update ProximityPresentationSessionTerminationErrorType Handling [#update-proximitypresentationsessionterminationerrortype-handling] If you switch over `ProximityPresentationSessionTerminationErrorType`, add handling for the new `Exception` value. This is a fallback when an unexpected issue occurs during presentation. ```diff await createProximityPresentationSession({ // ... other options onSessionTerminated: (error: PresentationSessionTerminationError | null) => { switch (error?.type) { // ... other errors + case "Exception": + // handle error + break; } }, }); ``` ### Update Function Calls for Device Key Authentication Policy [#update-function-calls-for-device-key-authentication-policy] If you use `generateDeviceKey`, `retrieveCredentials`, or `retrieveCredentialsUsingAuthorizationSession`, update calls to support the new optional `authenticationPolicy` parameter. ```diff const result = await generateDeviceKey({ // ... other options + authenticationPolicy: { + type: DeviceKeyAuthenticationType.BiometryCurrentSet, + }, }); ``` ### Update Error Handling for RetrieveCredentialsErrorTypes [#update-error-handling-for-retrievecredentialserrortypes] Add handling for the new `UnsupportedDeviceKeyAuthenticationPolicy` error. This occurs when the requested device key authentication method is not available on the device. ```diff const result = await retrieveCredentials( ... ); if (result.isErr()) { switch (result.error.type) { // ... other errors + case "UnsupportedDeviceKeyAuthenticationPolicy": + // handle error + break; } } ``` ### Update createProximityPresentationSession Calls [#update-createproximitypresentationsession-calls] Add the new optional `engagementFromNfc` parameter if you want to use buffered NFC engagement data. ```diff await createProximityPresentationSession({ // ... other options, + engagementFromNfc: true, }); ``` ### Update Android Manifest for web callback activity [#update-android-manifest-for-web-callback-activity] The shared `common` module has been removed and bundled into the underlying Android Holder SDK. If your Holder app uses web callbacks, update the `WebCallbackActivity` entry in your Android Manifest to use the new class path. This is **not** a general package import change. ```diff - + ``` # API Reference URL: /docs/issuance/authorization-code/authentication-provider/api-reference ## Configure an Authentication Provider [#configure-an-authentication-provider] ## Retrieve all Authentication Providers [#retrieve-all-authentication-providers] ## Retrieve an Authentication Provider [#retrieve-an-authentication-provider] ## Update an Authentication Provider [#update-an-authentication-provider] ## Delete an Authentication Provider [#delete-an-authentication-provider] # How to create an Authentication provider configuration URL: /docs/issuance/authorization-code/authentication-provider/guide An [authentication provider](/docs/issuance/authorization-code/authentication-provider/overview) (sometimes referred to as an Identity Provider, or IdP) is a platform typically used to store and manage user accounts on behalf of an organization or a service provider. MATTR VII uses the configured authentication provider to authenticate end users before issuing them credentials via the OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview). As part of configuring your OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview), you will need to create an authentication provider configuration on your MATTR VII tenant. ## Prerequisites [#prerequisites] * An existing Authentication provider that: * Exposes an OpenID Connect-based interface. * Is populated with users who can be issued verifiable credentials. * Is configured with application clients for which you hold the login credentials. ## Overview [#overview] Creating an Authentication provider configuration comprises the following steps: 1. [Create a MATTR VII Authentication provider configuration](#create-a-mattr-vii-authentication-provider-configuration). 2. [Update your Authentication provider callback URL](#update-your-authentication-provider-callback-url). ### Create a MATTR VII Authentication provider configuration [#create-a-mattr-vii-authentication-provider-configuration] This step can be performed via the MATTR Portal or an API request. 1. Log in to the [MATTR Portal](https://portal.mattr.global/). 2. In the navigation panel on the left-hand side, expand the **Credential Issuance** menu. 3. Select **Authentication provider**. 4. Insert your Authentication provider application `Domain` in the *Base URL* field. Make sure you prefix it with `https://`. 5. Insert your Authentication provider application `Client ID` in the *Client ID* field. 6. Insert your Authentication provider application `Client Secret` in the *Client secret* field. 7. Select **Create**. Make a request of the following structure to create an [Authentication provider configuration](/docs/issuance/authorization-code/authentication-provider/api-reference#configure-an-authentication-provider): ```http title="Request" POST /v1/users/authentication-providers ``` ```json title="Request body" { "url": "https://example.us.auth0.com/", "scope": ["openid", "profile", "email"], "clientId": "zH1IGGkRo6q0ofwiNJnlY0bg9fchw3s0", "clientSecret": "***********************************************************LFRrZ", "tokenEndpointAuthMethod": "client_secret_post", "staticRequestParameters": { "prompt": "login", "maxAge": 10000 }, "forwardedRequestParameters": ["login_hint"], "claimsToPersist": [] } ``` * `url` : The URL of your Authentication provider: * Must be a valid URL. * Must use the HTTPS protocol. * Must not be an IP address. * Must not include query parameters. * `scope` (*optional*): OpenID scopes to use during authentication. Each scope returns a set of user attributes which are called claims. Be sure to test that right scopes are added to get all the information you need. If no scopes are provided, the default scope of \[`openid`,`profile`,`email`] will be used. If any scopes are provided, `openid` must also be included in the array. * `clientId` : The client ID of the application client created on your Authentication provider. * `clientSecret` : The client secret of the application client created on your Authentication provider. * `tokenEndpointAuthMethod` : Authentication method for your Authentication provider token endpoint. The following methods are supported: * `client_secret_post` : Your credentials are passed as parameters in the request body. * `client_secret_basic` : Your credentials are passed as a base 64 encoded token. * `staticRequestParameters` (*optional*): Additional parameters that will be included in the request to your Authentication provider, and will be identical for every request as defined in your configuration. An example would be setting the `prompt` to be `login` to let your Authentication provider know it should show the login page every time. Keys must be strings. Values of top-level object keys must stringify to less than 1000 characters. * `forwardedRequestParameters` (*optional*): In contrast to `staticRequestParameters`, you can provide dynamic parameters that are fetched uniquely for each request to make the user journey more seamless. Here, you can forward params to your Authentication provider like `login_hint` which will pass the email of the user starting the flow. Forwarded parameters values are limited to 1000 characters each. * `claimsToPersist` (*optional*): List of claims to persist from your Authentication provider to MATTR VII. If you have attributes from the ID token (e.g. user identifier, email, etc.) that you would like persisted on MATTR VII, add them to this array. By default this array is empty, meaning no claims are persisted on MATTR VII. *Response* ```json title="Response body" { "id": "983c0a86-204f-4431-9371-f5a22e506599", // [!code focus] "redirectUrl": "https://learn.vii.au01.mattr.global/v1/oauth/authentication/callback", // [!code focus] "url": "https://example.us.auth0.com/", "scope": ["openid"], "clientId": "zH1IGGkRo6q0ofwiNJnlY0bg9fchw3s0", "clientSecret": "***********************************************************LFRrZ", // [!code focus] "tokenEndpointAuthMethod": "client_secret_post", "staticRequestParameters": { "prompt": "login", "maxAge": 10000 }, "forwardedRequestParameters": ["login_hint"], "claimsToPersist": [] } ``` * `id` : Unique identifier for the configured Authentication provider. This identifier can be used to retrieve, update or remove the Authentication provider configuration. * `redirectUrl` : This is the URL the user should be redirected to upon successful authentication to continue the OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview). You will add it to to your Authentication provider's allowlist/whitelist in the next step. * `clientSecret` : Your authentication provider client secret will be masked in the response without revealing the actual secret. If the secret is less than 20 characters it will be completely masked, and if it is over 20 only the last 5 characters are revealed. ### Update your Authentication provider callback URL [#update-your-authentication-provider-callback-url] Add the `redirectUrl` from the [response](#response) above to your Authentication provider's allowlist/whitelist of callback URLs: * If you are using [Auth0](https://auth0.com/) as your Authentication provider, this guide shows [how to add callback URLs](https://auth0.com/docs/authenticate/login/redirect-users-after-login). * If you are using a different authentication provider, consult their documentation for instructions on adding callback URLs. # Authentication provider URL: /docs/issuance/authorization-code/authentication-provider/overview ## Overview [#overview] An authentication or identity provider (IdP) is a platform used to store and manage user accounts. MATTR VII uses auth providers to authenticate users before issuing them credentials as part of the OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview). To configure a MATTR VII Authentication provider you will need an existing provider that exposes an OpenID Connect-based interface, populated with users who can be issued verifiable credentials. Your provider should allow creating application clients, which will be used by MATTR VII to generate the authentication requests when issuing credentials. Alongside authentication, you can configure MATTR VII to query your provider for different scopes of claims that can be retrieved and used as part of the OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview). Available scopes depend on your provider and its configuration. See [Auth0 supported scopes](https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes) as an example. Each MATTR VII tenant has a single Authentication provider which is used for all OID4VCI [Authorization Code flows](/docs/issuance/authorization-code/overview). ## Requirements [#requirements] You can use any OpenID provider if it supports the following capabilities specified by [OpenID Connect Core](https://openid.net/specs/openid-connect-core-1_0.html) and [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html): * Must publish its OpenID Provider configuration at /.well-known/openid-configuration * Must support Authorization Code flow * Must support the state parameter These requirements allow the OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview) to engage with your Authentication provider and accept an ID Token containing the user claims. ## Ensuring claim availability [#ensuring-claim-availability] When using the [Authorization Code flow](/docs/issuance/authorization-code/overview), the claims available for inclusion in the issued credential are determined by data returned by your Authentication provider and (if configured) your Interaction hook and/or Claims source. A common cause of issuance failures is a mismatch between the claims expected by your [Credential configuration](/docs/issuance/credential-configuration/overview) and the claims your Authentication provider actually supplies. To avoid this, it is important to understand the relationship between scopes and claims, and to verify that your provider can return the claims your credential configuration requires. ### Scopes and claims [#scopes-and-claims] In OpenID Connect, **claims** are the individual pieces of information about a user (e.g. `given_name`, `email`, `birthdate`). **Scopes** are high-level permissions that a client requests to gain access to categories of claims. The standard claim name in OIDC discovery for date of birth is `birthdate` (no underscore); you can later map this provider claim into a different credential claim name (for example, mapping OIDC `birthdate` to a credential claim `birth_date`) using claim mappings in your credential configuration. The OIDC specification defines recommended mappings between scopes and claims. For example: | Scope | Typical claims returned | | --------- | ---------------------------------------------------------- | | `openid` | `sub` | | `profile` | `name`, `given_name`, `family_name`, `nickname`, `picture` | | `email` | `email`, `email_verified` | | `address` | `address` | | `phone` | `phone_number`, `phone_number_verified` | However, these mappings are not guaranteed. Your Authentication provider ultimately decides which claims are returned based on: * The scopes requested by the client. * The provider's own configuration and custom mappings. * Whether the user's profile actually contains values for those claims. For example, requesting the `profile` scope does not guarantee that `given_name` will be returned. The user's profile must have a value for that field, and the provider must be configured to include it in the response. ### Checking your provider's supported claims [#checking-your-providers-supported-claims] Every OIDC-compliant provider publishes a discovery document at `/.well-known/openid-configuration`. This document includes two key fields for understanding claim availability: * `claims_supported`: Typically lists the specific claims the provider can return. This list may not be exhaustive, and additional claims may still be returned depending on your provider's configuration and custom mappings. * `scopes_supported`: Typically lists the scopes the provider accepts. Scopes that are not listed here may be ignored or rejected, but behaviour can vary across providers, so you should validate supported scopes by testing your integration (for example, by inspecting issued tokens and UserInfo responses). For example, a provider's discovery document might list: ```json title="Excerpt from /.well-known/openid-configuration" { "claims_supported": [ "sub", "name", "given_name", "family_name", "email", "email_verified", "picture" ], "scopes_supported": [ "openid", "profile", "email", "address" ] } ``` In this case, standard identity claims like `given_name` and `email` are supported. However, the standard OIDC claim `birthdate` is **not** listed in `claims_supported`, so the provider will not return it unless it is explicitly configured as a supported claim. Custom claim names (for example, `employee_id`) are provider-specific and will not appear in `claims_supported` unless your provider is configured to expose them. A claim being listed in `claims_supported` does not guarantee it will always be returned. It means the provider **can** return it — provided the right scopes are requested and the user's profile contains a value for that claim. ### Verifying claims for your credential configuration [#verifying-claims-for-your-credential-configuration] Before creating a credential configuration that relies on claims from your Authentication provider, follow these steps: 1. **Review your provider's discovery document**: Fetch the `/.well-known/openid-configuration` endpoint and check `claims_supported` against the claims your credential configuration requires. 2. **Confirm scope configuration**: Ensure the MATTR VII Authentication provider configuration requests the scopes needed to access those claims (e.g. `profile` for `given_name`, `email` for `email`). 3. **Check user profile data**: Verify that the user accounts in your provider have values populated for the required claims. Even supported claims will not be returned if the user's profile is missing that data. 4. **Handle unsupported provider claims and credential-only fields**: For claims that are not listed in `claims_supported` but you still want as OIDC claims (e.g. the standard `birthdate` claim), or for credential-specific fields that are not expected to come from your Authentication provider (e.g. an `expiry_date` field on the credential), consider the following options: * For provider claims, add them to your provider as custom claims using provider-specific features (e.g. Auth0 Actions or Rules) so they can appear in ID tokens or the UserInfo response. * For credential-only fields, use a [Claims source](/docs/issuance/claims-source/overview) or an Interaction hook to supply additional attributes from your own data, rather than expecting them from the Authentication provider. * Set a `defaultValue` in the credential configuration so issuance can succeed even when the claim is not provided by the provider or any other source. If a claim is marked as `required` in your [Credential configuration](/docs/issuance/credential-configuration/overview#claims-mapping) and is not returned by your Authentication provider (or any other configured claims source), and no `defaultValue` is set, credential issuance will fail. ## Request parameters [#request-parameters] You can configure MATTR VII to include request parameters when the holder is redirected to the Authentication provider: * Static request parameters are included in the request to your IdP, and will be identical for every request as defined in your configuration. An example would be setting the `prompt` to be `login` to let your Authentication provider know it should show the login page every time. * Keys must be strings. * Values of top-level object keys must stringify to less than 1000 characters. * Dynamic request parameters are fetched uniquely for each request to make the user journey more seamless. Here, you can forward params to your Authentication provider like `login_hint` which will pass the email of the user starting the flow. * Dynamic parameters values are limited to 1000 characters each. ## Persisting claims [#persisting-claims] Upon successful authentication the provider responds with an ID token. You can configure MATTR VII to persist selected claims on your tenant. By default no claims are persisted. It is highly recommended to take privacy considerations into account before configuring MATTR VII to persist any claims. # API Reference URL: /docs/issuance/authorization-code/interaction-hook/api-reference ## Create an Interaction hook [#create-an-interaction-hook] ## Retrieve Interaction hook [#retrieve-interaction-hook] # How to set up an Interaction hook URL: /docs/issuance/authorization-code/interaction-hook/guide An [Interaction hook](/docs/issuance/authorization-code/interaction-hook/overview) enables you to redirect users to a custom component during the OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview). This redirect happens after the user authenticates but before the credential is issued, allowing you to introduce custom interactions such as collecting additional information, performing MFA or biometric checks, or presenting terms of service. This guide walks you through the steps required to configure and integrate an Interaction hook into your credential issuance workflow. ## Prerequisites [#prerequisites] * A configured OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview) with an [Authentication provider](/docs/issuance/authorization-code/authentication-provider/overview). * A publicly accessible web or native application to serve as your Interaction hook component. We recommend using a web application as your Interaction hook component, as this is more compatible with most scenarios. ## Overview [#overview] Setting up an Interaction hook comprises the following steps: 1. [Configure the Interaction hook on your tenant](#configure-the-interaction-hook). 2. [Verify the Interaction hook redirect](#verify-the-interaction-hook-redirect). 3. [Process the interaction and redirect back to MATTR VII](#redirect-back-to-mattr-vii). ### Configure the Interaction hook [#configure-the-interaction-hook] Configure your MATTR VII tenant with the URL of your Interaction hook component. This tells MATTR VII where to redirect users after they authenticate. Each tenant can only have a single Interaction hook configured. If your workflow requires multiple custom interactions, they must be implemented within the same component. 1. Sign in to the [MATTR Portal](https://portal.mattr.global/). 2. In the left navigation, expand **Credential Issuance**. 3. Select **Interaction hook**. 4. In **Redirect URL post user completion**, enter the publicly accessible URL of your Interaction hook component. 5. Optionally configure the following: * **Claims**: Select which user claims from the Authentication Provider should be included in the session token sent to your component. * **Session timeout**: Set the session duration (in seconds) for the interaction. If not set, the default environment session duration applies. 6. Set **Status** to **Enabled**. 7. Select **Update** to save the configuration. 8. Copy the displayed **Secret**. You will need this to verify and sign JWTs. Make a PUT request to [configure the Interaction hook](/docs/issuance/authorization-code/interaction-hook/api-reference#create-an-interaction-hook): ```http title="Request" PUT /v1/openid/configuration ``` ```json title="Request body" { "interactionHook": { "url": "https://your-interaction-hook.example.com", "claims": ["first_name", "last_name", "email"], "sessionTimeoutInSec": 1200, "disabled": false } } ``` * `url` : The publicly accessible URL of your Interaction hook component. Must use HTTPS, must not be an IP address, and must not include query parameters. * `claims` : An array of user attribute names from the Authentication Provider that will be included in the session token sent to your component. Claims can only contain alphanumeric characters, `_`, or `-`. If empty or not defined, no claims are sent. * `sessionTimeoutInSec` : The session duration in seconds (minimum: 300, maximum: 7200). Once a session expires, the user is shown an error when redirected. If not specified, the default environment session duration applies. * `disabled` : Set to `false` to enable the Interaction hook. When `true` (default), the hook is disabled and users are not redirected. *Response* ```json title="Response body" { "interactionHook": { "url": "https://your-interaction-hook.example.com", "claims": ["first_name", "last_name", "email"], "sessionTimeoutInSec": 1200, "disabled": false, "secret": "dGtUrijBOT6UUJ8JO4kAFyGfhahDlVVeIk/sPbWTa7c=" // [!code focus] } } ``` * `secret` : A Base64-encoded 32-byte HMAC secret. Store this securely — you will use it to verify incoming JWTs and sign response JWTs. Once the Interaction hook is enabled, any OID4VCI Authorization Code issuance workflow on your tenant will redirect the user to your component after authentication. ### Verify the Interaction hook redirect [#verify-the-interaction-hook-redirect] When MATTR VII redirects the user to your component, it appends a signed JWT as a `session_token` query parameter: ``` https://your-interaction-hook.example.com?session_token=eyJhbGciOiJIUzI1NiIs... ``` Your component must verify this JWT using the `secret` obtained during configuration to confirm that the request originates from MATTR VII: 1. Extract the `session_token` query parameter from the incoming request URL. 2. Verify the JWT signature using the Base64-decoded `secret` with the HS256 algorithm. 3. Validate the following JWT claims: * `iss` : Must match your MATTR VII tenant URL. * `aud` : Must match your Interaction hook URL. * `exp` : Must not be expired. 4. If verification fails, reject the request. **Decoded JWT payload structure:** ```json title="Example decoded session_token payload" { "state": "hJvfiSp3eEGybd-KmL8ja", "scopes": ["ldp_vc:CourseCredential"], "claims": { "first_name": "Jane", "last_name": "Doe", "email": "jane@example.com" }, "authenticationProvider": { "url": "https://your-idp.example.com", "subjectId": "auth0|123456789" }, "redirectUrl": "https://your-tenant.vii.mattr.global/v1/oauth/interaction/hJvfiSp3eEGybd-KmL8ja/interactionhook/callback", "sub": "a44a7f92-c61e-48a0-88b6-863eeeb58394", "aud": "https://your-interaction-hook.example.com", "iss": "https://your-tenant.vii.mattr.global", "iat": 1673910963, "exp": 1673911263 } ``` * `state`: Unique session identifier. Must be included in your response JWT. * `claims`: User claims from the Authentication Provider (based on your configuration). * `redirectUrl`: The URL to redirect the user back to after the interaction hook custom component completes. * `authenticationProvider.url`: The Authentication Provider URL. * `authenticationProvider.subjectId`: The user's subject identifier with the Authentication Provider. The session token is passed in the URL and is subject to browser URL character limits. Avoid transferring large data (such as images) to or from the Interaction hook component. ### Redirect back to MATTR VII [#redirect-back-to-mattr-vii] Once the user completes the interaction, your component must redirect them back to MATTR VII to continue the issuance flow. This involves signing a new JWT and including it as a query parameter. **Steps to complete the redirect:** 1. **Sign a response JWT** using the same `secret` (Base64-decoded) with the HS256 algorithm: ```json title="Response JWT payload" { "iss": "https://your-interaction-hook.example.com", "aud": "https://your-tenant.vii.mattr.global", "state": "hJvfiSp3eEGybd-KmL8ja", "claims": { "preferred_name": "Jane Smith" }, "claimsToPersist": ["preferred_name"], "iat": 1673911000, "exp": 1673911060 } ``` * `iss` (required): Your Interaction hook URL. * `aud` (required): Your MATTR VII tenant URL. * `state` (required): The `state` value from the original session token. This links the response to the correct issuance session. * `claims` (optional): An object containing claims to include in the issued credential. These values can override claims from the Authentication Provider. * `claimsToPersist` (optional): An array of claim names from `claims` that should be persisted on the MATTR VII tenant for future use. If empty or omitted, no claims are persisted. * `iat` (required): Issued-at timestamp. * `exp` (required): Expiration timestamp. Use a short window (e.g. 1 minute) to minimize the risk of token replay. 2. **Redirect the user** to the `redirectUrl` from the original session token, appending the signed JWT as a `session_token` query parameter: ``` https://your-tenant.vii.mattr.global/v1/oauth/interaction/hJvfiSp3eEGybd-KmL8ja/interactionhook/callback?session_token=eyJhbGciOiJIUzI1NiIs... ``` MATTR VII validates the response JWT and uses the returned claims according to your [Credential configuration](/docs/issuance/credential-configuration/overview) claim mappings. #### Handling errors [#handling-errors] If your component determines that the user should not proceed with credential issuance (for example, they failed a biometrics check), you can signal an error by including an `error` object in the response JWT payload instead of `claims`: ```json title="Error response JWT payload" { "iss": "https://your-interaction-hook.example.com", "aud": "https://your-tenant.vii.mattr.global", "state": "hJvfiSp3eEGybd-KmL8ja", "error": { "message": "Biometric verification failed" }, "iat": 1673911000, "exp": 1673911060 } ``` The `error` object must include a `message` field (string). You can include additional fields alongside `message`, but only `message` is used by MATTR VII. **What happens when an error is returned:** 1. MATTR VII terminates the issuance session. 2. The user is redirected back to the wallet application with the following error parameters appended to the redirect URI: * `error`: `access_denied` * `error_description`: `Invalid interaction request` 3. The `error.message` from your JWT is included in the `OPENID_INTERACTION_HOOK_FAIL` [analytics event](/docs/platform-management/analytics/overview). This can be used for debugging and monitoring, but is not visible to the end user. Do not include personally identifiable information (PII) in the `error.message` field, as it is captured in analytics events and platform logs. ## Best practices [#best-practices] * **Secure your component:** Since the Interaction hook component must be publicly accessible, use the JWT verification mechanism to protect against unauthorized access. * **Keep sessions short:** Set `sessionTimeoutInSec` to an appropriate duration for your use case. Shorter sessions reduce the window for potential misuse. * **Use short-lived response JWTs:** Set the `exp` claim in your response JWT to a short window (e.g. 1 minute) to minimize the risk of token replay. * **Only persist necessary claims:** Use `claimsToPersist` selectively. Only persist claims that you need for future interactions to minimize data storage. * **Handle expiry gracefully:** If the session expires before the user completes the interaction, display a user-friendly message and guide them to restart the issuance flow. * **Combine interactions:** If you need multiple steps (e.g. biometric check followed by terms acceptance), build them into a single Interaction hook component. ## What's next? [#whats-next] * Follow the [Interaction hook tutorial](/docs/issuance/authorization-code/interaction-hook/tutorial) to see a step-by-step walkthrough using a sample application. * Review the [Interaction hook API reference](/docs/issuance/authorization-code/interaction-hook/api-reference) for full request and response schemas. # Interaction hook URL: /docs/issuance/authorization-code/interaction-hook/overview Many issuance workflows require the issuer to perform custom interactions with the user. This could include gathering more information, introducing additional authentication steps (e.g. MFA or biometric checks) or communicating the terms of service. To facilitate this you can configure MATTR VII to invoke an interaction hook which will redirect the user to a custom component. This redirect happens after the user is authenticated but before the credential is issued. The redirect can include any user claims retrieved from the Authentication provider, and these can be consumed and used by the interaction hook component. Interactions hooks are only available for the OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview) The interaction hook component must be publicly available, and thus requires access controls to protect it from unauthorized access. MATTR VII interaction hook integration supports a JWT HS256-based authorization scheme to enable this. When MATTR VII redirects the end user to your interaction hook component, it will include a JWT object as a `session_token` query parameter. Your interaction hook component should verify this JWT object using the interaction hook `secret` to make sure that the request is legitimate. This `secret` can be obtained when you [configure an interaction hook](/docs/issuance/authorization-code/interaction-hook/tutorial). If the verification fails, the request should be rejected. This token is passed in the URL, and is subject to the max URL character limits. Limits differ amongst browsers but 2000 characters should meet most browsers requirements. This means that you shouldn’t transfer big files (for example images) to and/or from the Interaction hook component. Once the end user completes the interaction hook journey, they need to be redirected back to MATTR VII to complete the issuance workflow. The redirect URL can be found in the session token JWT payload, and the issuer must generate and sign a new JWT token using the same `secret`, and include it as a query parameter with the redirect. Any claims returned by the interaction hook component can be included in the issued credential. However, they are not persisted on your tenant unless the interaction hook is explicitly configured to do. Upon the successful completion of the interaction hook, your custom component will redirect the user back to their digital wallet to complete the credential issuance flow. If your interaction hook component determines that the user should not be allowed to continue with the issuance journey (for example, they’ve failed a biometrics test), you can return an error in your signed JWT which will signal to the issuance journey that it should fail. ## Best practices [#best-practices] * Interaction hook components can be either a web or native application. We recommend using web applications as they are more compatible with most scenarios. * You can combine several custom interactions as part of the issuance workflow by building them into a single Interaction hook component. # Learn how to integrate an interaction hook into an OID4VCI workflow URL: /docs/issuance/authorization-code/interaction-hook/tutorial ## Introduction [#introduction] In an OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview) you can enhance the credential issuance process with custom interactions by configuring an [Interaction hook](/docs/issuance/authorization-code/interaction-hook/overview). This could include gathering additional information, introducing extra authentication steps, or communicating the terms of service. This allows you to create a more tailored and engaging credential issuance experience. Interaction hooks are only available for the OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview). This tutorial focuses on guiding you through the process of setting up an [OID4VCI workflow](/docs/issuance/authorization-code/overview) that redirects the user to a custom interaction where they can provide additional details that will be used in the issued credential. ## Prerequisites [#prerequisites] * Ensure you have completed the OID4VCI Authorization Code [tutorial](/docs/issuance/authorization-code/tutorial), as this tutorial builds upon it. In particular, ensure that you have the QR code generated in the [Create a credential offer step](/docs/issuance/authorization-code/tutorial#create-a-credential-offer). * To test locally you will need to expose your local interaction hook to the internet. You can do this by setting up a [free ngrok account](https://ngrok.com/), using a cloudflare tunnel or using your own solution. For this tutorial we will be using ngrok. Sign up for a free account at [ngrok.com](https://ngrok.com/). We recommend using the MATTR VII [Postman collection](/docs/api-reference#postman-collection) in this tutorial. While this isn't an explicit prerequisite it can really speed things up. ## Tutorial overview [#tutorial-overview] The current tutorial builds upon the OID4VCI Authorization Code [Tutorial](/docs/issuance/authorization-code/tutorial) by adding the following steps: 1. **Set up a sample web application:** Deploy a sample Next.js web app that acts as the custom interaction component in the issuance flow. In this example, the app lets users update their preferred name and pronoun, which will be included in the issued credential. You can extend this app to collect any additional information or add custom steps such as extra authentication, biometrics checks or liveness checks, as needed for your workflow. 2. **Integrate the Interaction hook with MATTR VII:** Configure MATTR VII to redirect the user to the Interaction hook as part of the issuance flow, and then handle any information returned by the Interaction hook as the user is redirected back to the issuance flow. The following diagram illustrates how an interaction hook fits into the OID4VCI Authorization Code issuance workflow: The workflow proceeds as follows: 1. **Accept credential offer:** The wallet accepts the credential offer and initiates the OID4VCI [Authorization Code flow](/docs/issuance/authorization-code/overview), redirecting the user to the Authentication Provider. 2. **Authenticate user:** The Authentication Provider authenticates the user and returns any associated claims to MATTR VII. 3. **Redirect to interaction hook:** MATTR VII redirects the user to the configured Interaction Hook component, including a signed `session_token` JWT as a query parameter. This token carries context such as user claims from the Authentication Provider and the redirect URL to return to after the interaction. 4. **Verify and interact:** The Interaction Hook component verifies the `session_token` using the shared `secret` to confirm the request is legitimate. The user then completes the custom interaction — this could be providing additional information, completing an MFA or biometrics check, or accepting terms of service. 5. **Return to MATTR VII:** Once the interaction is complete, the component signs a new JWT using the same `secret` — optionally including any additional claims — and redirects the user back to MATTR VII via the URL found in the session token payload. 6. **Map and issue:** MATTR VII combines claims from the Authentication Provider and the Interaction Hook, maps them according to the [credential configuration](/docs/issuance/credential-configuration/overview), and issues the signed credential to the wallet. ## Tutorial steps [#tutorial-steps] ### Start a ngrok tunnel on your local machine [#start-a-ngrok-tunnel-on-your-local-machine] 1. Start an ngrok tunnel in a new terminal window: ```bash title="Start a ngrok tunnel" ngrok http http://localhost:3000 ``` Your terminal window should show the tunnel details: ngrok tunnel 2. Make note of the `Forwarding` value, we will use it in the next step. ### Configure an Interaction hook on your MATTR VII tenant [#configure-an-interaction-hook-on-your-mattr-vii-tenant] This step can be performed via the MATTR Portal or an API request. 1. Sign in to the [MATTR Portal](https://portal.mattr.global/). 2. In the left navigation, expand **Credential Issuance**. 3. Select **Interaction hook**. 4. In **Redirect URL post user completion**, enter the `Forwarding` URL displayed in the terminal after starting the ngrok tunnel. This URL will be used to redirect the user to your custom interaction component during the OID4VCI workflow. 5. Set **Status** to **Enabled**. 6. Select **Update** to save the configuration. 7. Copy the displayed *Secret*. You will need it to [verify the interaction hook redirect](#verify-the-interaction-hook-redirect-jwt). Make a request of the following structure to [configure a new Interaction hook](/docs/issuance/authorization-code/interaction-hook/api-reference#create-an-interaction-hook): ```http title="Request" PUT /v1/openid/configuration ``` ```json title="Request body" { "interactionHook": { "url": "https://7658-3-24-140-84.ngrok-free.app", "disabled": false } } ``` * `url` : Replace with the `Forwarding` URL displayed in the terminal after starting the ngrok tunnel. This URL will be used to redirect the user to your custom interaction component during the OID4VCI workflow. * `disabled` : Set to `false` to enable the Interaction hook. If set to `true` (or if `disabled` is not provided), the Interaction hook will not be used during the OID4VCI workflow. *Response* ```json title="Response body" { "interactionHook": { "url": "https://7658-3-24-140-84.ngrok-free.app", "disabled": false, "secret": "uwIkhw************************************" // [!code focus] } } ``` * `secret` : The secret is a 32-byte array, provided as a Base64-encoded string. You will use this secret later in the tutorial. Once the Interaction hook is configured and enabled (`disabled` is set to `false`), any OID4VCI Authorization Code issuance workflow will redirect the user to the Interaction hook component after they have authenticated. Any tenant can only have a single Interaction hook configured. You can combine several custom interactions as part of the issuance workflow by building them into a single Interaction hook component. Currently there is nothing running on the Interaction hook URL, so the next step is to set up a local web application that the user can interact with. ### Setup a local web application as an Interaction hook component [#setup-a-local-web-application-as-an-interaction-hook-component] Next, you will set up a local Interaction hook component that allows the user to update their preferred name. MATTR VII will use this updated name in the issued credential. This component is a Next.js application that simulates a custom interaction, which the user is redirected to during the issuance flow. We are using Next.js because it allows us to show both the server side and client side responsibilities of the web app. Interaction hook components can be either a web or native application. We recommend using web applications as they are more compatible with most scenarios. Perform the following steps to setup the Interaction hook application: 1. Clone the [MATTR Sample Apps repo](https://github.com/mattrglobal/sample-apps) to your machine and navigate to the `interaction-hook-app` folder. 2. Rename the `.env-example` file to `.env`. 3. Open the `.env` file in a text editor and update the following variables: * `INTERACTION_HOOK_SECRET`: Replace this with the `secret` value returned in the response when you configured the Interaction hook in the previous step. * `APP_URL`: Replace this with the `Forwarding` URL displayed in the terminal after starting the ngrok tunnel. This URL will be used to redirect the user to your custom interaction component. * `ISSUER_TENANT_URL`: Replace this with the URL of your MATTR VII tenant, for example: `https://learn.vii.au01.mattr.global`. 4. Install and start the app by running the following command in the terminal: ```bash title="Install and run the app" npm install npm run dev ``` Your Interaction hook component is now set up and ready to be used in the OID4VCI workflow. Let's test that the interaction works as expected. ### Test the workflow [#test-the-workflow] To test this workflow you will use the credential offer created as part of the OID4VCI Authorization Code tutorial. 1. Open the GO hold example app. 2. Select **Scan**. 3. Scan the QR code generated when you [created the credential offer](/docs/issuance/authorization-code/tutorial#create-a-credential-offer). 4. Review the credential offer and select **Accept**. 5. Complete authentication. 6. Following authentication you will be redirected to the Interaction hook component. 7. Update your preferred name and select **Submit** (make sure you use a different name than the one you setup for your Auth0 user to see the difference in the issued credential). This will redirect you back to the OID4VCI workflow. 8. You will be redirected back to the OID4VCI workflow, where you can review the credential and select **Accept**. 9. The new credential will be issued using the name you provided in the Interaction hook component, replacing the original value retrieved from the authentication provider. Now that the workflow is working, let's break down our Interaction hook application and understand how it is integrated into the OID4VCI workflow. ## Breaking down the Interaction hook component [#breaking-down-the-interaction-hook-component] The sample interaction hook application provided in this tutorial is intentionally simple. While it contains some basic features that are not covered here, we will focus on the essential functionalities required for any Interaction hook component integrated with an OID4VCI Authorization Code flow: 1. **Use the Interaction hook JWT decoded payload**: The application can extract different claims from the decoded JWT payload and use them in the interaction. 2. **Sign the Interaction hook response JWT and redirect back to MATTR VII**: Once the user completes the interaction, the application must generate and sign a new JWT token and include it as a query parameter when redirecting the user back to MATTR VII. ### Use the Interaction hook JWT decoded payload [#use-the-interaction-hook-jwt-decoded-payload] MATTR VII redirects users to your Interaction hook component with a JWT object passed as a `session_token` query parameter, as shown in the following example: `https://7658-3-24-140-84.ngrok-free.app?session_token=ey...` Your application can decode this JWT object, extract different claims from the decoded payload and use them in the interaction. The following code snippet shows the structure of the decoded JWT payload retrieved from the Interaction hook configured above: ```json title="Decoded example JWT payload" { "state": "hJvfiSp3eEGybd-KmL8ja", "scopes": ["ldp_vc:CourseCredential"], "claims": { "name": "John Doe", "email": "example@example.com" }, "authenticationProvider": { "url": "https://myidentityprovider.auth0.com", "subjectId": "user|123456789" }, "redirectUrl": "https://learn.vii.au01.mattr.global/v1/oauth/interaction/hJvfiSp3eEGybd-KmL8ja/interactionhook/callback", "sub": "a44a7f92-c61e-48a0-88b6-863eeeb58394", "aud": "", "iss": "https://learn.vii.au01.mattr.global", "iat": 1673910963, "exp": 1673911263 } ``` * `state` : A unique value associated with each Interaction hook session. You will use it in the next step when signing the response JWT so that MATTR VII can match the response to the correct credential issuance flow. * `scopes` : Scopes retrieved from user authentication workflow. * `claims` : User claims defined when you [configured your interaction hook](#configure-a-mattr-vii-interaction-hook). * `authenticationProvider` : A provider that the user has authenticated with. * `url` : URL of the Authentication provider. * `subjectId` : Subject Identifier of the end user with this Authentication provider. * `redirectUrl` : URL to redirect to when users complete the Interaction hook journey. * `sub` : User identifier in MATTR VII. * `aud` : Interaction hook URL. * `iss` : Your MATTR VII tenant. * `iat` : Issued at (Epoch Unix timestamp) * `exp` : Expires at (Epoch Unix timestamp). Now let's take a look at how the sample application uses the `first_name` and `last_name` claims from the JWT payload to allow the user to update their preferred name: ```tsx title="src/app/page.tsx" import { decodeJwt } from 'jose' import { useRouter, useSearchParams } from 'next/navigation' import { useCallback, useMemo } from 'react' // Optional validation for claims // const passedClaims = z.object... export default function Home() { const router = useRouter() // Get the session token from the URL const searchParams = useSearchParams() const sessionToken = searchParams.get('session_token') // Decode the JWT to use the claims const decodedToken = useMemo(() => { if (!sessionToken) return null try { return decodeJwt(sessionToken) } catch (err) { return null } }, [sessionToken]) // Optional: Use ZOD to parse the claims from the decoded JWT // const claims = useMemo(() => { // if (!decodedToken) return null; // try { // const parsed = passedClaims.parse(decodedToken.claims); // return parsed; // } catch (err) { // return null; // } // }, [decodedToken]); const handleSubmit = useCallback(async () => { // Perform any last minute validation, etc. try { // Form Submission Handler - Step 4.5: Send data to backend API const response = await fetch('/api/interaction-hook', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: sessionToken, data: { // Pass your updated claims here } }) }) // Handle any errors... const result = await response.json() // The redirect URL contains the signed JWT response router.push(result.redirect) } catch (err) { console.error(err) } }, [sessionToken]) return
// Render the UI form - use the claims to pre-fill the form
} ``` 1. The decoded JWT payload is passed into the rendered React page and the `name` claim is used to pre-fill a form. 2. The user can update their name and submit the form, which triggers the next step in which the application signs a new JWT and redirects the user back to MATTR VII.
### Sign the Interaction hook response JWT and redirect back to MATTR VII [#sign-the-interaction-hook-response-jwt-and-redirect-back-to-mattr-vii] Once the user completes the interaction (in our example this is when they submit the form), your application must sign a new JWT using the secret provided by MATTR VII and redirect the user back to MATTR VII. This allows the issuance flow to continue with the updated information. Your application backend must first ensure that the original request is legitimate and originates from MATTR VII by verifying the JWT object using the interaction hook `secret` provided in the response when you [configured the Interaction hook](#configure-an-interaction-hook-on-your-mattr-vii-tenant). Here's how the sample application generates and signs the response JWT: ```ts title="src/app/api/interaction-hook/route.ts" import { jwtVerify, SignJWT, type JWTVerifyResult } from 'jose' // Optional request schema // const requestSchema = z.object... export async function POST(request: Request) { try { // Parse request from client const body = await request.json() // Optional: use ZOD to parse & validate the request // const { token, data } = requestSchema.parse(body); const { token, data } = body // Load environment variables const secret = process.env.INTERACTION_HOOK_SECRET const issuerUrl = process.env.ISSUER_TENANT_URL const appUrl = process.env.APP_URL // Error handling... // MATTR VII provides the secret as base64-encoded when you create the interaction hook const secretBuffer = Buffer.from(secret, 'base64') // Verify the JWT from MATTR VII let verifiedJwt: JWTVerifyResult try { verifiedJwt = await jwtVerify(token, secretBuffer, { issuer: issuerUrl, audience: appUrl }) } catch (verifyError: unknown) { return Response.json({ error: 'Invalid session token' }, { status: 401 }) } // Sign new JWT with update claims const responseJwt = await new SignJWT({ iss: appUrl, // Issuer: Your app aud: issuerUrl, // Audience: MATTR VII tenant state: verifiedJwt.payload.state, // Session state from original request // Custom claims to be added to the credential claims: { name: data.name, pronouns: data.pronouns }, // Claims to persist in MATTR VII (empty in this example) // You could store data here for future interactions claimsToPersist: [] }) .setProtectedHeader({ alg: 'HS256', typ: 'JWT' }) .setIssuedAt() .setExpirationTime('1m') // JWT expires in 1 minute .sign(secretBuffer) // Construct return URL for the frontend to redirect to const redirectUrl = `${verifiedJwt.payload.redirectUrl}?session_token=${responseJwt}` // return response to the client return Response.json({ redirect: redirectUrl, // Include debug info in development only ...(process.env.NODE_ENV === 'development' && { debug: { claimsProcessed: Object.keys(data), jwtCreated: true } }) }) } catch (error) { // Handle any errors... } } ``` 1. The handler verifies the original `session_token` and extracts the `state`, `redirectUrl`, and other identifiers. 2. It signs a new JWT (`interaction_token`) with the updated `preferred_name` claim. The `interaction_token` must include the `state` value from the original session to ensure that MATTR VII can match the response to the correct credential issuance flow. 3. The response is a 302 redirect back to the `redirectUrl`, with the signed JWT included as a query parameter. 4. MATTR VII will validate this signed JWT and continue the issuance process using the data you've included in the response. This means that you can use the new `claims.preferred_name` value in the issued credential.
## What's next? [#whats-next] Check out more resources on MATTR Learn that will enable you to: * Configure a [Claims source](/docs/issuance/claims-source/tutorial) to retrieve data from compatible data sources and use it in the issued credential. * Apply branding to issued credentials as part of creating a [Credential configuration](/docs/issuance/credential-configuration/guide). # Document signers URL: /docs/issuance/certificates/api-reference/document-signers ## Create a Document signer [#create-a-document-signer] ## Revoke a Document signer [#revoke-a-document-signer] ## Retrieve all Document signers [#retrieve-all-document-signers] ## Retrieve a Document signer [#retrieve-a-document-signer] ## Update a Document signer [#update-a-document-signer] ## Delete a Document signer [#delete-a-document-signer] # IACA URL: /docs/issuance/certificates/api-reference/iaca ## Create an IACA [#create-an-iaca] ## Retrieve all IACAs [#retrieve-all-iacas] ## Retrieve an IACA [#retrieve-an-iaca] ## Update an IACA [#update-an-iaca] ## Delete an IACA [#delete-an-iaca] # Issuer metadata URL: /docs/issuance/certificates/api-reference/issuer-metadata ## Retrieve issuer metadata [#retrieve-issuer-metadata] # CWT Credentials URL: /docs/issuance/credential-configuration/api-reference/cwt ## Create a CWT Credential configuration [#create-a-cwt-credential-configuration] ## Retrieve all CWT Credential configurations [#retrieve-all-cwt-credential-configurations] ## Retrieve a CWT Credential configuration [#retrieve-a-cwt-credential-configuration] ## Update a CWT Credential configuration [#update-a-cwt-credential-configuration] ## Delete a CWT Credential configuration [#delete-a-cwt-credential-configuration] # mDocs Credentials URL: /docs/issuance/credential-configuration/api-reference/mdocs ## Create an mDocs Credential configuration [#create-an-mdocs-credential-configuration] ## Retrieve all mDocs Credential configurations [#retrieve-all-mdocs-credential-configurations] ## Retrieve an mDocs Credential configuration [#retrieve-an-mdocs-credential-configuration] ## Update an mDocs Credential configuration [#update-an-mdocs-credential-configuration] ## Delete an mDocs Credential configuration [#delete-an-mdocs-credential-configuration] # Semantic CWT Credentials URL: /docs/issuance/credential-configuration/api-reference/semantic-cwt ## Create a Semantic CWT Credential configuration [#create-a-semantic-cwt-credential-configuration] ## Retrieve all Semantic CWT Credential configurations [#retrieve-all-semantic-cwt-credential-configurations] ## Retrieve a Semantic CWT Credential configuration [#retrieve-a-semantic-cwt-credential-configuration] ## Update a Semantic CWT Credential configuration [#update-a-semantic-cwt-credential-configuration] ## Delete a Semantic CWT Credential configuration [#delete-a-semantic-cwt-credential-configuration] # Revocation lists URL: /docs/issuance/revocation/api-reference/cwt-revocation-lists ## Retrieve all CWT credentials revocation lists [#retrieve-all-cwt-credentials-revocation-lists] ## Retrieve a CWT credential revocation list [#retrieve-a-cwt-credential-revocation-list] # Status update URL: /docs/issuance/revocation/api-reference/cwt-status-update ## Update CWT credential status [#update-cwt-credential-status] ## Retrieve CWT credential status [#retrieve-cwt-credential-status] # Status List Configuration URL: /docs/issuance/revocation/api-reference/mdocs-status-list-configuration ## Create a Status List configuration [#create-a-status-list-configuration] ## Retrieve all Status List configurations [#retrieve-all-status-list-configurations] ## Retrieve a Status List configuration [#retrieve-a-status-list-configuration] ## Update a Status List configuration [#update-a-status-list-configuration] ## Delete a Status List configuration [#delete-a-status-list-configuration] # Status List retrieval URL: /docs/issuance/revocation/api-reference/mdocs-status-list-retrieval ## Retrieve all Status lists [#retrieve-all-status-lists] ## Retrieve a Status list [#retrieve-a-status-list] ## Retrieve a Status list token [#retrieve-a-status-list-token] ## Status list distribution [#status-list-distribution] # Status List Signers URL: /docs/issuance/revocation/api-reference/mdocs-status-list-signers ## Create a Status List Signers [#create-a-status-list-signers] ## Revoke a Status List Signer [#revoke-a-status-list-signer] ## Retrieve all Status List Signers [#retrieve-all-status-list-signers] ## Retrieve a Status List Signer [#retrieve-a-status-list-signer] ## Update a Status List Signer [#update-a-status-list-signer] ## Delete a Status List Signer [#delete-a-status-list-signer] # Status update URL: /docs/issuance/revocation/api-reference/mdocs-status-update ## Update mDocs status [#update-mdocs-status] ## Retrieve mDocs status [#retrieve-mdocs-status] # Revocation lists URL: /docs/issuance/revocation/api-reference/semantic-cwt-revocation-lists ## Retrieve all Semantic CWT credentials revocation lists [#retrieve-all-semantic-cwt-credentials-revocation-lists] ## Retrieve a Semantic CWT credential revocation list [#retrieve-a-semantic-cwt-credential-revocation-list] # Status update URL: /docs/issuance/revocation/api-reference/semantic-cwt-status-update ## Update Semantic CWT credential status [#update-semantic-cwt-credential-status] ## Retrieve Semantic CWT credential status [#retrieve-semantic-cwt-credential-status] # Verification request signers URL: /docs/verification/certificates/api-reference/verification-request-signers ## Create a Verification request signer [#create-a-verification-request-signer] ## Retrieve all Verification request signers [#retrieve-all-verification-request-signers] ## Retrieve a Verification request signer [#retrieve-a-verification-request-signer] ## Update a Verification request signer [#update-a-verification-request-signer] ## Delete a Verification request signer [#delete-a-verification-request-signer] # Verifier root CA certificates URL: /docs/verification/certificates/api-reference/verifier-root-ca-certificates ## Create a Verifier root CA certificate [#create-a-verifier-root-ca-certificate] ## Retrieve all Verifier root CA certificates [#retrieve-all-verifier-root-ca-certificates] ## Retrieve a Verifier root CA certificate [#retrieve-a-verifier-root-ca-certificate] ## Update a Verifier root CA certificate [#update-a-verifier-root-ca-certificate] ## Delete a Verifier root CA certificate [#delete-a-verifier-root-ca-certificate] # Handling verification results URL: /docs/verification/remote-mobile-verifiers/guides/handling-verification-results Description: Learn how to interpret mDoc verification results returned by the mDocs Verifier SDK in remote mobile presentation flows. When a holder responds to a remote mobile verification request, the mDocs Verifier SDK returns structured data containing the verification outcome. This guide explains the response structure and how to interpret it. ## What gets verified [#what-gets-verified] Before working through the response structure, it is worth being clear about what is actually being verified. The credential itself, the full mDoc as issued and stored in the holder's wallet, never leaves the wallet and is never shared with a verifier. What the holder shares is a *credential presentation*: a subset of the credential's data, accompanied by the issuer's signature over the released items and a device authentication that proves the holder's device authorized this specific presentation. This model is grounded in [ISO/IEC 18013-7](https://www.iso.org/standard/91154.html), which defines remote mDL and mDoc verification and specifies the wire protocols used to exchange a presentation between holder and verifier: the OpenID for Verifiable Presentations (OID4VP) protocol (Annex B), and the W3C Digital Credentials (DC) API integration (Annex C and Annex D). Regardless of the wire protocol, what the holder shares is a presentation derived from the credential rather than the credential itself, and the presentation carries the same cryptographic proof and integrity guarantees as the credential it is drawn from. When you verify a response, you are verifying the credential presentation. The cryptographic trust checks (issuer signature, issuer trust, validity, revocation, device key binding) apply to that presented slice, not to the credential as a whole on the holder's device. The MATTR VII `verificationResult.verified` flag reflects whether this presented credential passed those checks. The rest of this guide uses "credential" as shorthand for the presented credential when the context makes clear we are talking about what was returned in the response. ## How results are delivered [#how-results-are-delivered] In a remote mobile presentation flow, the holder's wallet app responds to a verification request initiated by your verifier app on the same device. After the holder consents to sharing their credentials, the SDK returns an `OnlinePresentationSessionResult` object. Your application receives the result inline from the SDK and is responsible for parsing and acting on it. ## Understanding the response [#understanding-the-response] An `OnlinePresentationSessionResult` contains: * **`challenge`** (optional): The unique challenge of the session, used to verify response authenticity. * **`mobileCredentialResponse`** (optional): A `MobileCredentialResponse` containing the credential data returned by the holder. * **`error`** (optional): An error object if the presentation workflow could not be completed. When the holder responds to a verification request, the result will either contain a `mobileCredentialResponse` or an `error`. From Android Verifier SDK v7.0.0, `OnlinePresentationSessionResult` is a sealed interface with `Success` (carrying `mobileCredentialResponse`) and `Failure` (carrying `error`) variants — branch over it with `when`/`is` rather than inspecting nullable fields. From iOS Verifier SDK v6.0.0, it is a `@frozen` enum with `success(sessionId:challenge:mobileCredentialResponse:)` and `failure(sessionId:challenge:error:)` cases — branch over it with `switch`/`case`. The serialized structure documented on this page is the same across platforms. ### Session-level errors [#session-level-errors] If the `error` field is present, the holder was unable to complete the presentation workflow. The error object contains: * **`type`**: Indicates why the presentation failed * **`message`**: A human-readable description of the error The `type` values are: * **`SessionAborted`**: The user explicitly aborted the session before it completed. * **`ResponseError`**: An error occurred while processing the response from the holder's wallet. * **`VerificationError`**: The submitted presentation response is invalid. * **`WalletUnavailable`**: The wallet appears to be unavailable and unable to respond to the request. * **`Unknown`**: An unknown error occurred while processing the presentation response. **Example**: The user cancels the mDL presentation request: ```json title="Example session error when the user aborts the session" { "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "error": { "type": "SessionAborted", "message": "User aborted the session" } } ``` ### Credential response [#credential-response] When no session-level error occurs, the `mobileCredentialResponse` field contains the holder's response. A `MobileCredentialResponse` has two optional fields: * **`credentials`**: An array of `MobileCredentialPresentation` objects representing credentials that were successfully returned. * **`credentialErrors`**: An array of `CredentialError` objects representing credentials that couldn't be returned. A successful session (no `error`) can still result in several credential-level outcomes, often in combination. Each one is described along two independent axes: whether the requested credential itself verified, and whether the requested claims within it were provided. 1. **Requested credential not presented**: The holder doesn't have the requested credential, so there is nothing to verify. The credential appears under `credentialErrors` with an error code of `notReturned`, and no `verificationResult` is produced. 2. **Presented credential not verified**: A credential was provided but the credential-level trust checks failed. The `credentials` array contains the credential with `verified: false` and a `reason.type` explaining why. Even if claims were returned, they should not be relied on while the credential itself did not verify. 3. **Presented credential verified, all claims provided**: The credential-level checks pass (`verified: true` with no `reason`) and every requested claim is returned (no `claimErrors`). Both layers are clean. 4. **Presented credential verified, some claims missing**: The credential-level checks pass (`verified: true`), but one or more requested claims were not returned and appear under `claimErrors`. The credential is trustworthy; the data payload is incomplete. How to handle the missing data is up to the relying party, depending on its use case. Options include failing the flow, falling back to collecting the required data through another channel, or proceeding if the missing claim is not essential. ## Detailed result structure [#detailed-result-structure] ### Credential-level information [#credential-level-information] Each `MobileCredentialPresentation` in the `credentials` array contains: * **`docType`**: The credential type (e.g., `org.iso.18013.5.1.mDL` for a mobile driver's license). * **`claims`**: Verified claims organized by namespace. * **`claimErrors`**: Errors for individual claims that couldn't be verified or were not returned. * **`validityInfo`**: Credential validity period timestamps (`signed`, `validFrom`, `validUntil`, `expectedUpdate`). * **`verificationResult`**: Verification status containing: * **`verified`**: Boolean indicating if verification succeeded. This is a high-level result; individual claim errors may still exist. * **`reason`** (optional): Object explaining verification failures when `verified` is `false`. Contains a `type` value from the following: * `DeviceKeyInvalid`: Device key is not valid. This can occur if the credential was not properly bound to the device or if the device's secure element is compromised. * `InvalidSignerCertificate`: Invalid signer certificate. This can occur if the credential's signing certificate is not valid or has been tampered with. * `IssuerNotTrusted`: Credential was issued by a certificate that is not trusted by the verifier. This can occur if the issuer's certificate is not included in the verifier's trusted issuer list. * `MobileCredentialExpired`: Credential expired. This can occur if the current date is after the credential's `validUntil` date. * `MobileCredentialInvalid`: Credential is not valid. This can occur for various reasons such as failing signature verification, containing invalid data, or not conforming to expected formats. * `MobileCredentialNotYetValid`: Credential not yet valid. This can occur if the current date is before the credential's `validFrom` date. * `StatusRevoked`: Credential has been revoked. This can occur if the issuer has revoked the credential after it was issued, which is typically checked through a revocation mechanism provided by the issuer. * `StatusUnknown`: Credential status could not be determined. This can occur if the verifier is unable to check the revocation status of the credential due to network issues or if the issuer does not provide a revocation mechanism. * `TrustedIssuerCertificateExpired`: Trusted issuer certificate expired. This can occur if the certificate of the trusted issuer has passed its expiration date, which may affect the trustworthiness of credentials issued by that issuer. * `TrustedIssuerCertificateNotYetValid`: Trusted issuer certificate not yet valid. This can occur if the certificate of the trusted issuer is not yet valid (i.e., the current date is before the certificate's `validFrom` date), which may affect the trustworthiness of credentials issued by that issuer. * `UnsupportedCurve`: Credential object contains an unsupported curve. This can occur if the credential uses cryptographic curves that are not supported by the verifier's cryptographic library, which may prevent successful verification of the credential's signatures. * **`issuerInfo`** (optional): Issuer details including `commonName` and `trustedIssuerId`. * **`branding`** (optional): Visual information for displaying the credential (name, description, colors, logos). You can use this to create a rich user interface when showing credential details in your application. The failure object serializes as the `reason` field. In the native iOS (v6.0.0+) and Android (v7.0.0+) Verifier SDKs, it is accessed in code via the `failureType` property (it still serializes as `reason`). In the React Native SDK it is accessed as `reason`. ### Claim-level information [#claim-level-information] Claims are organized by namespace within the `claims` object. For example, for an mDL, claims appear under the `org.iso.18013.5.1` namespace: ```json title="Example claims structure for a verified mDL credential" "claims": { "org.iso.18013.5.1": { "family_name": { "value": "Smith" }, "given_name": { "value": "Jane" }, "birth_date": { "value": "1990-05-15" }, "address": { "value": "123 Main Street, Springfield" } } } ``` If specific claims couldn't be verified or weren't provided, they appear in the `claimErrors` object with the same namespace structure and an error code of `notReturned`: ```json title="Example claim errors for a credential with a missing claim" "claimErrors": { "org.iso.18013.5.1": { "portrait": "notReturned" } } ``` ### Credential errors [#credential-errors] When a requested credential wasn't provided by the holder, it appears in the `credentialErrors` array with a `docType` and an `errorCode` of `notReturned`: ```json title="Example credential errors for a missing credential" "credentialErrors": [ { "docType": "org.iso.18013.5.1.mDL", "errorCode": "notReturned" } ] ``` ## Understanding the `verified` flag [#understanding-the-verified-flag] With the structure in mind, it is worth being explicit about what `verificationResult.verified` does and does not tell you, because it is the single most important field for deciding whether to trust a presentation. `verified` is the pass/fail flag for the presented credential. It applies to the slice of credential data the holder released in this session, together with its associated issuer signature and device authentication, not to the credential as a whole as stored on the holder's device. When `verified: true`, what the holder presented has passed MATTR VII's cryptographic and trust checks: the issuer signature on the released items is intact, the credential was issued by a trusted issuer, it is not expired or revoked, and the device key binding holds. In plain terms, the presented mDoc is genuine, has not been tampered with, and is currently valid. What `verified: true` does not mean is that every claim you requested came back. `verified` is a high-level result on the credential as a whole, and individual claim errors may still exist. A credential can return `verified: true` while a specific requested claim (for example, `portrait`) is missing and surfaces under `claimErrors` with an error code of `notReturned`. The credential is trustworthy; the data payload may still be incomplete. The mirror image is `verified: false`. When the credential layer fails, MATTR VII surfaces a `reason.type` explaining why (for example, expired, revoked, untrusted issuer, invalid signer certificate, or broken device key binding). The `reason` field is typed as optional, so defensive code should handle the case where it is absent. See the [credential-level information](#credential-level-information) section above for the full list of `reason.type` values. ### Two layers of checks [#two-layers-of-checks] A relying party's business logic needs to check two distinct things: 1. **Is the credential real and valid?** Look at `verificationResult.verified` on each credential in the `credentials` array. This is the objective trust check, and it should not be overridden by business logic. 2. **Did we get all the data we asked for?** Look at `claimErrors` on each credential, and at `credentialErrors` on the credential response. Whether a missing claim or credential is acceptable is a business-logic decision specific to your use case. In other words, `verified` is the objective measure of what can or cannot be accepted at all. Everything else, including `claimErrors`, `credentialErrors`, and the specific claim values returned, feeds your business logic about whether to accept the presentation for your particular use case. Treating `verified: true` as a green light to proceed without inspecting `claimErrors` is a common integration mistake. The credential may be cryptographically valid while still missing a field your use case requires. For example, an age-restricted purchase flow needs `birth_date`, but if only `family_name` came back the relying party cannot make an age decision even though the credential itself returned `verified: true`. Conversely, bypassing a `verified: false` result with business logic (for example, accepting an expired or revoked credential because the claims look correct) defeats the trust model and should not be done. `verified` is MATTR VII's API shape. The underlying trust evaluation for mDL and mDoc credentials follows ISO/IEC 18013-5, but the `verified` boolean itself is not a standards-defined field. ## Complete examples [#complete-examples] ### Session error [#session-error] The wallet couldn't be reached: ```json title="Example response for a wallet unavailable error" { "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "error": { "type": "WalletUnavailable", "message": "The wallet is unavailable" } } ``` ### Requested credential not presented [#requested-credential-not-presented] Neither layer of the check produces a result here. The holder did not provide a matching mDL, so the credential layer has nothing to verify (no `verificationResult` is returned) and the claims layer is not applicable. The missing credential is reported under `credentialErrors`, and the relying party's business logic must decide how to handle it. ```json title="Example response for a requested credential that was not presented" { "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "mobileCredentialResponse": { "credentialErrors": [ { "docType": "org.iso.18013.5.1.mDL", "errorCode": "notReturned" } ] } } ``` ### Presented credential not verified [#presented-credential-not-verified] The credential layer fails. The holder provided an mDL but the credential-level trust checks failed (in this example, because the credential has expired): `verified: false`, with `reason.type` identifying why. Even if claims were returned, they should not be relied on while the credential itself did not verify. The credential should not be accepted regardless of business logic. ```json title="Example response for a presented credential that did not verify (expired mDL)" { "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "mobileCredentialResponse": { "credentials": [ { "docType": "org.iso.18013.5.1.mDL", "verificationResult": { "verified": false, "reason": { "type": "MobileCredentialExpired", "message": "Credential has expired" } }, "validityInfo": { "signed": "2024-01-15T10:00:00Z", "validFrom": "2024-01-15T10:00:00Z", "validUntil": "2025-01-15T10:00:00Z", "expectedUpdate": "2026-01-15T10:00:00Z" } } ] } } ``` ### Presented credential verified, all claims provided [#presented-credential-verified-all-claims-provided] Both layers pass. The credential layer returns `verified: true` with no `reason`, confirming the mDL is genuine, current, and from a trusted issuer. The claims layer returns every requested claim with no `claimErrors`, so the relying party has the complete data payload it asked for. ```json title="Example response for a presented credential that verified with all requested claims returned" { "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "mobileCredentialResponse": { "credentials": [ { "docType": "org.iso.18013.5.1.mDL", "claims": { "org.iso.18013.5.1": { "family_name": { "value": "Smith" }, "given_name": { "value": "Jane" }, "birth_date": { "value": "1990-05-15" }, "address": { "value": "123 Main Street, Springfield" } } }, "validityInfo": { "signed": "2024-01-15T10:00:00Z", "validFrom": "2024-01-15T10:00:00Z", "validUntil": "2027-01-15T10:00:00Z", "expectedUpdate": "2028-01-15T10:00:00Z" }, "verificationResult": { "verified": true }, "issuerInfo": { "commonName": "State Department of Motor Vehicles", "trustedIssuerId": "d4a6e9f2-3b1c-4d8e-a5f7-9c2b0e8d1a3f" }, "branding": { "name": "Driver License", "description": "State-issued driver license", "backgroundColor": "#1E3A8A", "issuerLogo": { "format": "svg", "data": "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==" } } } ] } } ``` ### Presented credential verified, some claims missing [#presented-credential-verified-some-claims-missing] The credential layer passes but the claims layer is incomplete. The mDL itself returns `verified: true` and is trustworthy, while one requested claim (`portrait`) was not released and appears under `claimErrors` as `notReturned`. What is missing here is data, not trust, and the relying party's business logic must decide whether the missing claim is acceptable for its use case. ```json title="Example response for a presented credential that verified with a missing claim" { "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "mobileCredentialResponse": { "credentials": [ { "docType": "org.iso.18013.5.1.mDL", "claims": { "org.iso.18013.5.1": { "family_name": { "value": "Smith" }, "given_name": { "value": "Jane" }, "birth_date": { "value": "1990-05-15" } } }, "claimErrors": { "org.iso.18013.5.1": { "portrait": "notReturned" } }, "validityInfo": { "signed": "2024-01-15T10:00:00Z", "validFrom": "2024-01-15T10:00:00Z", "validUntil": "2027-01-15T10:00:00Z", "expectedUpdate": "2028-01-15T10:00:00Z" }, "verificationResult": { "verified": true }, "issuerInfo": { "commonName": "State Department of Motor Vehicles", "trustedIssuerId": "d4a6e9f2-3b1c-4d8e-a5f7-9c2b0e8d1a3f" } } ] } } ``` ## Recommended handling flow [#recommended-handling-flow] When processing an `OnlinePresentationSessionResult`, follow these steps: 1. **Check for session-level errors**: If the `error` field is present, the presentation workflow failed before any credential data was returned. Handle based on the `error.type` value. 2. **Validate the challenge**: Compare the returned `challenge` against the one you generated when creating the session. This protects against session replay attacks. 3. **Check for credential errors**: Inspect `mobileCredentialResponse.credentialErrors` to determine if any requested credentials were not returned. Handle these based on whether the credential is required for your use case. 4. **Iterate through credentials**: For each `MobileCredentialPresentation` in `mobileCredentialResponse.credentials`: * Check `verificationResult.verified`. If `false`, inspect `verificationResult.reason` to understand why. * If `verified` is `true`, proceed to extract claim values from the `claims` object. * Check `claimErrors` for any claims that were requested but not returned. Decide whether to proceed based on which claims are missing. 5. **Apply business logic**: Use the verified claims, issuer information, and branding data to make authorization decisions and present results in your application. Challenge validation protects against session replay attacks. Generate a unique challenge when creating the session, and verify it matches the one returned in the result. ## Next steps [#next-steps] * Review the mDocs Verifier SDK API reference for [iOS](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/) or [Android](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/) for complete type definitions * Explore the [remote mobile verification workflow](/docs/verification/remote-mobile-verifiers/workflow) for the complete end-to-end process * Follow the [quickstart](/docs/verification/remote-mobile-verifiers/quickstart) or [tutorial](/docs/verification/remote-mobile-verifiers/tutorial) for practical implementation examples # Build a mobile application that can verify an mDoc from an Apple Wallet installed on the same device URL: /docs/verification/remote-mobile-verifiers/guides/verify-with-apple-wallet ## Overview [#overview] This guide demonstrates how to use the [mDocs Verifier Mobile SDK](/docs/verification/remote-mobile-verifiers/sdks/overview) to build a mobile application that can verify an [mDoc](/docs/concepts/mdocs) presented from an Apple Wallet, using the [Verify with Wallet API](https://developer.apple.com/wallet/get-started-with-verify-with-wallet/). The guide covers both the **iOS** and **React Native** mDocs Verifier SDKs. Verify with Apple Wallet is an Apple-only API, so there is no Android coverage. Even when you build your verifier app with React Native, the Verify with Apple Wallet flow only runs on iOS. Use the tabs throughout the guide to follow the steps for your chosen SDK. ## Prerequisites [#prerequisites] Before you get started, let's make sure you have everything you need. * You will need access to a [MATTR VII tenant](/docs/resources/get-started) and know how to [make API requests](/docs/platform-management/portal#interacting-with-the-tenant). ### MATTR Resources [#mattr-resources] As part of your MATTR Pi SDK onboarding process you should have been provided with access to the SDK resources for your chosen platform: * ZIP file which includes the required framework: (`MobileCredentialVerifierSDK-*version*.xcframework.zip`). * Sample Verifier app: You can use this app for reference as you work through this guide. This guide is only meant to be used with the most [recent version](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog) of the iOS mDocs Verifier SDK. * The [@mattrglobal/mobile-credential-verifier-react-native](https://www.npmjs.com/package/@mattrglobal/mobile-credential-verifier-react-native) npm package, provided as part of your MATTR Pi SDK onboarding process. Verify with Apple Wallet support was added to the React Native mDocs Verifier SDK in **v9.0.0** (functional from **v9.0.1**) and requires **iOS 16 or above**. Although the app is built with React Native, the Verify with Apple Wallet flow only runs on iOS. Calling it on Android returns a `PlatformNotSupported` error. This guide is only meant to be used with the most [recent version](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html) of the React Native mDocs Verifier SDK. ### Apple resources [#apple-resources] * Submit an [entitlement request](https://developer.apple.com/contact/request/verify-with-wallet/) to use the Verify with Wallet API. This request would include your app information as well as what information you would like to request for verification and why. You can only proceed with this guide after your request has been approved by Apple. * For testing the end-to-end flow, you will need to install the [Wallet Identity Developer profile](https://developer.apple.com/bug-reporting/profiles-and-logs/?name=wallet\&platform=ios). Got everything? Let's get going! ## Workflow [#workflow] The following diagram depicts the workflow you will build in this guide: 1. The user performs an action in a mobile app (**Verifier Application**) that requires a credential to be presented for verification. 2. The **Verifier Application** interacts with the **Verify with Wallet API** to validate the request can be met using the **Apple Wallet**. This includes validating: * The identity of the verifier. * That the verifier is entitled to verify the requested credential type. * That the user has a matching credential in their **Apple Wallet**. 3. The **Verify with Wallet API** returns the results of the entitlement validation to the **Verifier Application**. 4. If (and only if) all entitlements are valid, the **Verifier Application** presents a *Verify with Apple Wallet* button to the **user**. 5. The **user** taps the *Verify with Apple Wallet* button. 6. The **Verifier Application** starts a Verify with Apple Wallet presentation session with the configured **MATTR VII tenant**. 7. The **MATTR VII tenant** calls the **Verify with Wallet API** with a credentials request. 8. The **Verify with Wallet API** invokes the user's **Apple Wallet** to request the credential. 9. The **Apple Wallet** displays a popup interface to the **user** requesting their consent to share the requested information (they never leave the **Verifier Application**). 10. The **user** provides their consent to sharing a specific credential from the **Apple Wallet**. 11. The user's **Apple Wallet** sends an Apple encrypted credential to the **MATTR VII tenant**. 12. The **MATTR VII tenant** decrypts and verifies the credential. 13. The **MATTR VII tenant** sends the verification results to the **Verifier Application**. 14. The **Verifier Application** displays the verification results and the **user** continues the interaction accordingly. You will build this workflow in three parts: 1. [Part 1: Setup the MATTR VII Verifier tenant](#part-1-setup-the-mattr-vii-verifier-tenant). 2. [Part 2: Configure your Apple Developer account](#part-2-configure-your-apple-developer-account). 3. [Part 3: Build a mobile application](#part-3-build-a-mobile-application). ## Part 1: Setup the MATTR VII Verifier tenant [#part-1-setup-the-mattr-vii-verifier-tenant] The MATTR VII tenant will be used to interact with your mobile application (generating a verification request) and the wallet application (presenting an mDoc for verification) as per OID4VP and ISO/IEC 18013-7 Annex B. To enable this, you must: 1. [Create a verifier application configuration](#create-a-verifier-application-configuration): Define what applications can create verification sessions with the MATTR VII tenant, and how to handle these requests. 2. [Create a trusted wallet provider configuration](#create-a-trusted-wallet-provider-configuration): Define how to invoke specific wallet applications as part of a remote verification workflow. 3. [Configure a trusted issuer](#configure-a-trusted-issuer): The MATTR VII verifier tenant will only accept mDocs issued by these trusted issuers. ### Create a verifier application configuration [#create-a-verifier-application-configuration] Each MATTR VII tenant can interact with multiple verifier applications, and can handle requests differently for each application. This means you must create a verifier application configuration that defines how to handle verification requests from your mobile application. The verifier application *Type* is always `iOS` for the Verify with Apple Wallet flow, regardless of the SDK you build with. The credential is always presented through iOS/Apple Wallet, even when your verifier app is built with React Native. 1. In the navigation panel on the left-hand side, expand the **Credential Verification** menu. 2. Select **Applications**. 3. Select **Create new**. 4. Enter a meaningful *Name* for your application (e.g. "Verify with Wallet Application"). 5. Use the *Type* dropdown to select `iOS` as you are building an iOS application. 6. Enter your Apple Developer Team ID in the *Team ID* field. You can find it in the *Membership details* section of your [Apple Developer account](https://developer.apple.com/account). 7. Enter your iOS application bundle identifier in the *Bundle ID* field. This is the unique identifier of your iOS application, which you can set in your [Xcode project settings](https://developer.apple.com/documentation/xcode/preparing-your-app-for-distribution/#Set-the-bundle-ID). This will be used by the MATTR VII tenant to validate incoming requests are from a known and trusted application. 8. Select **Create**. Make the following request to your MATTR VII tenant to [create a verifier application configuration](/docs/verification/remote-verification-api-reference/verifier-applications#create-a-verifier-application): ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "Verify with Wallet Application", "type": "ios", "teamId": "A2B3C4D5E6", "bundleId": "io.mattrlabs.dev.sampleApp.MdocSampleApp", "resultAvailableInFrontChannel": true } ``` * `name` : You can use whatever name you'd like, as long as it is unique on your tenant. * `type` : Use `ios` as you are building an iOS verifier application. * `teamId` : Replace with your Apple Developer Team ID associated with your iOS application. You can find it in the *Membership details* section of your [Apple Developer account](https://developer.apple.com/account). * `bundleId` : Replace with your iOS application bundle identifier. This is the unique identifier of your iOS application, which you can set in your [Xcode project settings](https://developer.apple.com/documentation/xcode/preparing-your-app-for-distribution/#Set-the-bundle-ID). This must match the bundle ID you included in your Verify with Apple Wallet entitlement request. * `resultAvailableInFrontChannel` : Setting this to `true` makes the verification results available directly to the verifier application. *Response* ```json title="Response body" { "id": "0eaa8074-8cc4-41ec-9e42-072d36e2acb0" //... rest of application configuration } ``` * `id` : You will use this value later to initialize the SDK so that requests coming from your verifier application can be recognized and trusted by the MATTR VII tenant. ### Configure a trusted issuer [#configure-a-trusted-issuer] You must configure trusted issuers on your MATTR VII verifier tenant, as presented mDocs will only be verified if they had been issued by a trusted issuer. This is achieved by providing the PEM certificate of the IACA used by these issuers to sign mDocs. 1. In the navigation panel on the left-hand side, expand the **Credential Verification** menu. 2. Click on **Trusted issuers**. 3. Click on **Create new**. 4. In the *Certificate PEM file* field, paste the PEM certificate of the IACA used by the issuers you want to trust. For testing purposes, you can use the IACA of the [Apple Developer Integrator profile](https://developer.apple.com/bug-reporting/profiles-and-logs/?name=wallet) referenced in the [prerequisites](#prerequisites) section. 5. Click on **Add**. Make the following request to your MATTR VII tenant to [configure a trusted issuer](/docs/verification/remote-verification-api-reference/trusted-issuers#create-a-trusted-issuer): ```http title="Request" POST /v2/credentials/mobile/trusted-issuers ``` ```json title="Request body" { "certificatePem": "-----BEGIN CERTIFICATE-----\nMIICYzCCAgmgAwIBAgIKXhjLoCkLWBxREDAKBggqhkjOPQQDAjA4MQswCQYDVQQG\nEwJBVTEpMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0Ew\nHhcNMjQwMTE4MjMxNDE4WhcNMzQwMTE1MjMxNDE4WjA4MQswCQYDVQQGEwJBVTEp\nMCcGA1UEAwwgbW9udGNsaWZmLWRtdi5tYXR0cmxhYnMuY29tIElBQ0EwWTATBgcq\nhkjOPQIBBggqhkjOPQMBBwNCAASBnqobOh8baMW7mpSZaQMawj6wgM5e5nPd6HXp\ndB8eUVPlCMKribQ7XiiLU96rib/yQLH2k1CUeZmEjxoEi42xo4H6MIH3MBIGA1Ud\nEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRFZwEOI9yq\n232NG+OzNQzFKa/LxDAuBgNVHRIEJzAlhiNodHRwczovL21vbnRjbGlmZi1kbXYu\nbWF0dHJsYWJzLmNvbTCBgQYDVR0fBHoweDB2oHSgcoZwaHR0cHM6Ly9tb250Y2xp\nZmYtZG12LnZpaS5hdTAxLm1hdHRyLmdsb2JhbC92Mi9jcmVkZW50aWFscy9tb2Jp\nbGUvaWFjYXMvMjk0YmExYmMtOTFhMS00MjJmLThhMTctY2IwODU0NWY0ODYwL2Ny\nbDAKBggqhkjOPQQDAgNIADBFAiAlZYQP95lGzVJfCykhcpCzpQ2LWE/AbjTGkcGI\nSNsu7gIhAJfP54a2hXz4YiQN4qJERlORjyL1Ru9M0/dtQppohFm6\n-----END CERTIFICATE-----" } ``` * `certificatePem` : Replace with the PEM certificate of the IACA used by the issuers you want to trust. For testing purposes, you can use the IACA of the [Apple Developer Integrator profile](https://developer.apple.com/bug-reporting/profiles-and-logs/?name=wallet) you installed as part of the prerequisites. *Response* A successful `201` response indicates that this issuer's certificate was added to your MATTR VII tenant's trusted issuer's list. This means that mDocs that use this IACA as their root certificate can be trusted and verified. ### Create an Apple Identity Access certificate [#create-an-apple-identity-access-certificate] To enable your MATTR VII tenant to interact with Apple Wallet as part of a remote verification workflow, you must create an Apple Identity Access certificate. This certificate is used to sign requests sent to the Verify with Wallet API. Currently this action is not available in the MATTR Portal and can only be performed via an API request. Make the following request to your MATTR VII tenant to [create an Apple Identity Access certificate](/docs/verification/remote-verification-api-reference/apple-identity-access-csr#create-an-apple-identity-access-certificate-signing-request): ```http title="Request" POST /v2/presentations/certificates/apple-identity-access-certificates ``` ```json title="Request body" { "teamId": "A2B3C4D5E6", "merchantId": "com.domain.subdomain" } ``` * `teamId` : Replace with your Apple Developer Team ID associated with your iOS application. You can find it in the *Membership details* section of your [Apple Developer account](https://developer.apple.com/account). * `merchantId` : Replace with the merchant ID you created as part of your [Verify with Apple Wallet entitlement request](https://developer.apple.com/contact/request/verify-with-wallet/). *Response* A successful `201` response indicates that the Apple Identity Access certificate was created successfully: ```json title="Response body" { "id": "fd44e792-45ac-11f0-bef8-bb24f133065e", "teamId": "A2B3C4D5E6", "merchantId": "com.domain.subdomain", "csrPem": "string" // [!code focus] } ``` Make note of the `csrPem` value, as you will need it in the next part to create a matching Apple Identity Access certificate in your Apple Developer account. ## Part 2: Configure your Apple Developer account [#part-2-configure-your-apple-developer-account] Now that you have created the Apple Identity Access certificate in your MATTR VII tenant, you must create a matching certificate in your Apple Developer account and configure it accordingly. ### Create an Identity Access Certificate [#create-an-identity-access-certificate] Log into your [Apple Developer account](https://developer.apple.com/account) and create a new Apple Pay Identity Access certificate using the CSR you obtained in the previous step. For detailed instructions, see [Create an Apple Pay Identity Access certificate](https://developer.apple.com/help/account/capabilities/configure-apple-pay/). Once this certificate is created, the Verify With Wallet API will be able to validate requests coming from your MATTR VII tenant, as they are signed using the private key associated with this certificate. ## Part 3: Build a mobile application [#part-3-build-a-mobile-application] Now that the MATTR VII verifier tenant and your Apple developer account are both properly configured, you can proceed with the steps required to embed verification capabilities into your mobile verifier application. Use the tabs in each step to follow the instructions for your chosen SDK: ### Environment setup [#environment-setup] #### Create a new project [#create-a-new-project] Follow the detailed instructions to [Create a new Xcode Project](https://help.apple.com/xcode/mac/current/#/dev07db0e578) and add your organization's identifier. #### Unzip the dependencies file [#unzip-the-dependencies-file] 1. Unzip the [`MobileCredentialVerifierSDK-*version*.xcframework.zip` file](#mattr-resources). 2. Drag the `MobileCredentialVerifierSDK-*version*.xcframework` folder into your project. 3. Configure `MobileCredentialVerifierSDK.xcframework` to [Embed & sign](https://help.apple.com/xcode/mac/current/#/dev51a648b07). See [Add existing files and folders](https://help.apple.com/xcode/mac/current/#/dev81ce1d383) for detailed instructions. This should result in the following framework being added to your project: Framework added #### Run the application [#run-the-application] Select **Run** and make sure the application launches with a *“Hello, world!”* text in the middle of the display, as shown in the following image:
Application ready
#### Install the SDK [#install-the-sdk] Add the React Native mDocs Verifier SDK to your project: ```bash title="Install the SDK" yarn add @mattrglobal/mobile-credential-verifier-react-native ``` #### Generate the native iOS project [#generate-the-native-ios-project] This guide uses [Expo](https://docs.expo.dev/) with development builds. Generate the native `ios/` project so you can add the required capabilities in Xcode: ```bash title="Generate the native project" yarn expo prebuild ``` #### Run the application [#run-the-application-1] Connect a **physical iOS device** and run the application on it: ```bash title="Run iOS application" yarn ios --device ``` Verify with Apple Wallet cannot be tested on an iOS simulator. You must run the application on a physical iOS device (iOS 16 or above) to complete the flow with a wallet installed on the same device.
### Configure application entitlement files [#configure-application-entitlement-files] To enable your application to interact with the Apple Wallet, you must add the following capabilities to your Xcode project: 1. Add the *In App Identity Presentment* capability and define the document types and elements you want to request for verification. You can find a list of supported document types and elements in the [Apple Developer documentation](https://developer.apple.com/documentation/passkit/configuring-your-environment-for-the-verify-with-wallet-api#Configure-your-apps-entitlement-file). 2. Add the *In App Identity Presentment Merchant IDs* and define the merchant IDs you created as part of your [Verify with Apple Wallet entitlement request](https://developer.apple.com/contact/request/verify-with-wallet/). You can add these via the *Signing & Capabilities* tab of your Xcode project. For detailed instructions, see [Add capabilities to your app](https://developer.apple.com/documentation/xcode/adding-capabilities-to-your-app). **Identity Presentment capabilities** Identity Presentment capabilities **Identity Presentment capabilities configuration** Entitlement files configuration Once `yarn expo prebuild` has generated the `ios/` project, open it in Xcode and add the same capabilities described above via the *Signing & Capabilities* tab, exactly as in the iOS steps. For detailed instructions, see [Add capabilities to your app](https://developer.apple.com/documentation/xcode/adding-capabilities-to-your-app). **Identity Presentment capabilities** Identity Presentment capabilities **Identity Presentment capabilities configuration** Entitlement files configuration Re-running `yarn expo prebuild --clean` regenerates the native `ios/` project and may discard manual Xcode changes. Re-apply these capabilities if you regenerate the project. ### Initialize the SDK [#initialize-the-sdk] The first capability you will build into your app is to initialize the SDK so that the app can use its functions and classes. From v6.0.0, `initialize` is asynchronous and requires a `PlatformConfiguration` with both your tenant host and Verifier Application `id` (this drives [SDK Tethering](/docs/verification/sdks/sdk-tethering)): ```swift title="Initialize the SDK" let platformConfiguration = PlatformConfiguration( tenantHost: URL(string: "https://learn.vii.au01.mattr.global")!, // [!code highlight] applicationId: "" ) try await mobileCredentialVerifier.initialize(platformConfiguration: platformConfiguration) ``` * `tenantHost` : Replace with the URL of your MATTR VII tenant. * `applicationId` : The `id` returned when you [created the verifier application](#create-a-verifier-application-configuration). Import the SDK and call `initialize` with a `platformConfiguration` that sets your tenant host. The function returns a [`neverthrow`](https://github.com/supermacro/neverthrow) `Result`, so errors are surfaced via `result.isErr()` rather than a thrown exception: ```tsx title="Initialize the SDK" import { initialize } from "@mattrglobal/mobile-credential-verifier-react-native"; const result = await initialize({ platformConfiguration: { tenantHost: "https://learn.vii.au01.mattr.global" }, // [!code highlight] }); if (result.isErr()) { // handle initialization error } ``` * `tenantHost` : Replace with the URL of your MATTR VII tenant. In the React Native SDK, the Verifier Application `id` is provided later, when you call `fetchAppleWalletConfiguration`, rather than at initialization. ### Validate verification feasibility [#validate-verification-feasibility] Before displaying a Verify with Apple Wallet button, the SDK must confirm that the request can be met using the Apple Wallet. This includes validating: * The identity of the verifier (using the provided `merchantId`). * That the verifier is entitled to verify the requested credential type (using the provided `docType` in the credential request and comparing it against the entitlements associated with this `merchantId`). * That the user has a credential that matches the requested `docType` in their **Apple Wallet**. This is achieved by calling the `fetchAppleWalletConfiguration` method and providing a `MobileCredentialRequest` (and a `merchantId`). The request defines what information is requested for verification and is used by the Verify with Wallet API to determine whether the requested credential type is supported by the entitlements associated with your application. The `merchantId` is the merchant ID you created as part of your [Verify with Apple Wallet entitlement request](https://developer.apple.com/contact/request/verify-with-wallet/), and is used to determine whether this specific merchant is authorized to request the specified credential type. The `MobileCredentialRequest` defines what information is required for verification: * The requested credential type (e.g. `org.iso.18013.5.1.mDL`). * The claims required for verification (e.g. `family_name`). * The requested namespace (e.g. `org.iso.18013.5.1`). * Whether or not the verifier intends to persist the claim value (`true`/`false`). Call the [`fetchAppleWalletConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialverifier/fetchapplewalletconfiguration\(request:merchantid:\)) method: ```swift title="Fetch Apple Wallet configuration" func fetchAppleWalletConfiguration( request: MobileCredentialRequest, merchantId: String ) async -> Result ``` If all checks pass, this method returns an instance of the [AppleWallet](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/applewallet) class, which includes the UI elements and methods required to request a credential from an Apple Wallet. Your application can then present one of the returned UI elements (for example a *Verify with Apple Wallet* button) to the user, which when tapped would invoke the Apple Wallet to request the credential for verification. Verify with Apple Wallet button **MobileCredentialRequest example** ```swift title="Swift" let mobileCredentialRequest = MobileCredentialRequest( docType: "org.iso.18013.5.1.mDL", namespaces: [ "org.iso.18013.5.1": [ "family_name": false, "given_name": false, "birth_date": false ] ] ) ``` Call `fetchAppleWalletConfiguration` with the `request`, your verifier `applicationId`, and the `merchantId`. It returns a `Result`: ```tsx title="Fetch Apple Wallet configuration" import { fetchAppleWalletConfiguration } from "@mattrglobal/mobile-credential-verifier-react-native"; const result = await fetchAppleWalletConfiguration({ request: mobileCredentialRequest, applicationId: "", merchantId: "merchant.com.example.app", }); if (result.isErr()) { // request cannot be met via Apple Wallet, do not show the button return; } const appleWallet = result.value; ``` * `applicationId` : The `id` returned when you [created the verifier application](#create-a-verifier-application-configuration). Unlike the iOS SDK, the React Native `AppleWallet` type exposes **only** a `requestMobileCredentials` method. It does **not** return an SDK-rendered button or any UI elements. Your app renders its own *Verify with Apple Wallet* button, enabling it only after `fetchAppleWalletConfiguration` succeeds. **MobileCredentialRequest example** ```tsx title="React Native" const mobileCredentialRequest = { docType: "org.iso.18013.5.1.mDL", namespaces: { "org.iso.18013.5.1": { family_name: false, given_name: false, birth_date: false, }, }, }; ``` ### Request a credential from an Apple Wallet [#request-a-credential-from-an-apple-wallet] Once the user taps the *Verify with Apple Wallet* button, you can call the `requestMobileCredentials` method on the `AppleWallet` instance to invoke the Apple Wallet and request the mDoc for verification. * `challenge` : Unique value that must be generated by the verifier app. Should be a unique, unpredictable value generated for each verification session to mitigate replay attacks by ensuring the response from the Apple Wallet is tied to the current request and cannot be reused maliciously. Always generate a new challenge for every credential request. Once called, this method orchestrates the interaction to request the mDoc for verification from the Apple Wallet. The user sees the Apple Wallet popup interface, where they can select the credential to present for verification and provide their consent to share the requested information. Call the [`requestMobileCredentials`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/applewallet/requestmobilecredentials\(challenge:\)) method: ```swift title="Request a credential from Apple Wallet" func requestMobileCredentials(challenge: String) async throws -> OnlinePresentationSessionResult ``` Call `requestMobileCredentials` on the `appleWallet` instance returned by `fetchAppleWalletConfiguration`. It returns a `Result`: ```tsx title="Request a credential from Apple Wallet" const result = await appleWallet.requestMobileCredentials(challenge); if (result.isErr()) { // handle error return; } const sessionResult = result.value; ``` ### Display verification results and continue the interaction [#display-verification-results-and-continue-the-interaction] After the user consents, Apple Wallet sends the encrypted credential to the MATTR VII tenant. The tenant decrypts and verifies it, then returns the verification results to your app as an `OnlinePresentationSessionResult`. The results are returned as an [`OnlinePresentationSessionResult`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/onlinepresentationsessionresult). Your app should then: * Validate the challenge in the `OnlinePresentationSessionResult` matches the one you sent in the request. * Parse the `OnlinePresentationSessionResult` object to extract the verification results. * Display the results to the user and continue the interaction accordingly. After checking `result.isErr()` on the value returned by `requestMobileCredentials`, read the `OnlinePresentationSessionResult` from `result.value` (the `sessionResult` in the previous step). Your app should then: * Validate the challenge in the `sessionResult` matches the one you sent in the request. * Parse the `sessionResult` object to extract the verification results. * Display the results to the user and continue the interaction accordingly.
# Android Verifier SDK v6.0.0 Migration Guide URL: /docs/verification/remote-mobile-verifiers/sdks/android-6.0.0-migration-guide ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in MobileCredentialVerifierSDK v6.0.0 for Android, including breaking changes, new features, and migration steps. ## Key Features [#key-features] * **Improved reliability in contactless flows**: Enhanced BLE performance delivers more consistent proximity credential exchanges and faster engagements. * **Stronger cryptography and standards alignment**: Updated COSE algorithms (as per RFC 9864) strengthen cryptographic compatibility and ensure continued compliance with evolving standards. * **Simpler, Decoupled Releases**: The Android Verifier SDK no longer has a shared common module. This allows us to decouple the releases of Holder and Verifier and versions no longer need to match. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency across mobile environments, and improve overall user experience. For a detailed list of changes included in this release, refer to the [SDK Changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/). ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v6.0.0 that require updates to your existing implementation: | # | Element | Change | Impact | | - | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------- | | 1 | `MobileCredentialVerifier.sendProximityPresentationRequest(skipStatusCheck = ...)` | Parameter renamed to `checkStatus = ...` with **inverted semantics** | All call sites using `skipStatusCheck = ...` must be updated. | | 2 | `mattr.global.mobilecredentials.common` | Package path moved to `mattr.global.mobilecredentials.verifier` | All imports must be updated. | ## Bug Fixes [#bug-fixes] * Fixed parsing of status lists that use a bit size other than 2. * Fixed an issue where the Wallet could attempt to start a new session before the previous session had completed. * Resolved BLE retry issues during proximity presentations that resulted in "SDK not ready (Engaging)" errors. * Improved cross-device flow handling in DCM scenarios and resolved result mismatches. * Corrected UI refresh issues following automatic session termination. ## Minimum Requirements [#minimum-requirements] * Android 7 / Nougat / API 24. * The Android Verifier SDK is built using Kotlin 2.0. This adds some intrinsic dependencies into your build tools. * Kotlin 2.0 is supported from AGP version [8.5](https://developer.android.com/build/kotlin-support). * AGP 8.5 is supported from Gradle version [8.7](https://developer.android.com/build/releases/about-agp) and Android Studio Koala [2024.1.1](https://developer.android.com/studio/releases). ## Migration Steps [#migration-steps] ### Rename any references to the `common` package to `verifier` [#rename-any-references-to-the-common-package-to-verifier] The shared `common` module has been removed and bundled into the Android Verifier SDK. The package path has moved from `mattr.global.mobilecredentials.common` to `mattr.global.mobilecredentials.verifier`. Update all imports accordingly: ```diff - import mattr.global.mobilecredentials.common.* + import mattr.global.mobilecredentials.verifier.* ``` This can be done with a global find and replace across your codebase. If you are using both the Holder and Verifier SDKs, you will need to limit your search to the relevant parts of your application. ### Remove the Common dependency from your project [#remove-the-common-dependency-from-your-project] The Common dependency is now bundled into the Verifier SDK. Remove it from your project's `build.gradle` or `build.gradle.kts` file: ```diff dependencies { implementation("global.mattr.mobilecredentials:verifier:6.0.0") - implementation("global.mattr.mobilecredentials:common:5.x.x") } ``` ### Update `sendProximityPresentationRequest` calls [#update-sendproximitypresentationrequest-calls] The `skipStatusCheck` parameter has been renamed to `checkStatus` with inverted semantics. When `checkStatus` is `true` (default), the SDK will verify credential status. When `false`, it will skip status checking. Update all calls accordingly: ```diff - val response = verifier.sendProximityPresentationRequest(request = requests, skipStatusCheck = true) + val response = verifier.sendProximityPresentationRequest(request = requests, checkStatus = false) ``` | Old Parameter | New Parameter | Mapping | | ----------------------------------- | ------------------------------ | ------------------ | | `skipStatusCheck = false` (default) | `checkStatus = true` (default) | No change needed | | `skipStatusCheck = true` | `checkStatus = false` | Invert the boolean | # Android Verifier SDK v7.0.0 Migration Guide URL: /docs/verification/remote-mobile-verifiers/sdks/android-7.0.0-migration-guide Description: A comprehensive guide to migrating to Android Verifier SDK v7.0.0, covering breaking changes, new features, and step-by-step migration instructions. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in the Android Verifier SDK v7.0.0, including breaking changes, new features, and migration steps. This release focuses on strengthening trust between your verifier application and your MATTR VII tenant, improving consistency across platforms, and making verification results more predictable. The headline change is **SDK Tethering**, which becomes **required** in this release: every SDK and app instance is now registered with, and licensed by, your MATTR VII tenant at initialization. Unlike the Holder SDK, where SDK Tethering is optional, **SDK Tethering is required for the Verifier SDK** from v7.0.0. This builds on an existing requirement — remote mobile (app-to-app) verification already required you to supply a `platformConfiguration` so the SDK could reach your MATTR VII tenant to handle the backend verification. That `platformConfiguration` is now mandatory for all initializations and additionally drives SDK Tethering. ## Key Features [#key-features] * **SDK Tethering (required)**: The Android Verifier SDK is now tethered to a MATTR VII tenant, tying each SDK/app instance to your tenant. On first initialization the SDK registers the app instance with the tenant specified in `PlatformConfiguration` and obtains a license; on subsequent initializations the existing license is renewed automatically. This lets you view registered and active app instances directly from your tenant for operational insight, and establishes a remote management channel we expect to extend in future releases (for example, remote syncing of trusted issuer lists and eventing). The SDK uses [Key Attestation](https://source.android.com/docs/security/features/keystore/attestation) during app registration. Network access is required when registration or renewal is performed. * **Cross-platform alignment**: Verification result types and the revocation status list API have been renamed and restructured to align with the iOS Verifier SDK, minimizing divergence for teams maintaining cross-platform applications. * **More predictable session results**: `OnlinePresentationSessionResult` and the revocation status list refresh result are now sealed interfaces with explicit `Success` and `Failure` variants, removing ambiguity from result handling. * **Simpler remote mobile configuration**: The application ID for remote mobile (app-to-app) verification is now taken from `PlatformConfiguration`, so it no longer needs to be passed on every request. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency, and improve overall reliability. ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v7.0.0 that require updates to your existing implementation: | # | Change | Impact | | - | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | SDK Tethering is now required: `PlatformConfiguration` is mandatory on `initialize`, which now also registers the app instance and obtains a license | Always supply a `PlatformConfiguration`. Handle the new `InvalidLicenseException` and `FailedToRegisterException`, which can be thrown by `initialize` and by most SDK APIs. | | 2 | `MobileCredentialVerifier.updateTrustedIssuerStatusLists` renamed to `refreshRevocationStatusLists` | Update all references to the new method name. | | 3 | `MobileCredentialVerifier.getTrustedIssuerStatusListsCacheInfo` renamed to `getRevocationStatusListsCacheInfo` | Update all references to the new method name. | | 4 | `TrustedIssuerStatusListsCacheInfo` renamed to `RevocationStatusListsCacheInfo` | Update all code that constructs or references this type. | | 5 | `UpdateTrustedIssuerStatusListsResult` renamed to `RevocationStatusListsRefreshResult` and converted to a sealed interface; the `.success: Boolean` field is removed | Replace `if (result.success)` with `when`/`is` pattern matching over `Success` and `Failure`. | | 6 | `OnlinePresentationSessionResult` converted to a sealed interface with `Success` and `Failure` variants | Replace field-based branching with `when`/`is` pattern matching. | | 7 | `MobileCredentialResponse.credentials` and `MobileCredentialResponse.credentialErrors` are now required non-nullable `List` fields | Remove null-check branches and `?.` navigation; both arguments must be provided at construction. | | 8 | `applicationId` parameter removed from `requestMobileCredentials` (remote mobile, app-to-app) | Remove the `applicationId` argument and supply it via `PlatformConfiguration` instead. | ## Migration Steps [#migration-steps] ### Create a verifier application on your MATTR VII tenant [#create-a-verifier-application-on-your-mattr-vii-tenant] SDK Tethering requires a verifier application configured on the MATTR VII tenant your SDK connects to. If you already use remote mobile (app-to-app) verification you will have created one; the same application is reused for tethering. If you have not, create one now. To register your Android application, make a request to create a verifier application: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584b6f6a892d356899fb9576c5f226a179e6199f2b7a1d837b5c234c5a8e" ] } ``` * `name`: A unique name to identify your verifier application. * `type`: Must be `android` for an Android application. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/verification/android-app-signing) for more information. The response will include a unique `id` for your application, used by the SDK to identify and authenticate your application. ### Supply `PlatformConfiguration` and handle tethering errors at initialization [#supply-platformconfiguration-and-handle-tethering-errors-at-initialization] `PlatformConfiguration` is now required on `initialize`. Previously it was optional and only used for remote mobile (app-to-app) verification flows; it now also drives SDK Tethering, registering the app instance with your MATTR VII tenant and obtaining a license on first initialization. Always pass a `PlatformConfiguration`, and handle the new `InvalidLicenseException` and `FailedToRegisterException` that `initialize` can now throw: ```diff - val platformConfiguration = PlatformConfiguration( - tenantHost = URL("https://your-tenant.vii.mattr.global") - ) - MobileCredentialVerifier.initialize(context, platformConfiguration) + val platformConfiguration = PlatformConfiguration( + tenantHost = URL("https://your-tenant.vii.mattr.global"), + applicationId = "1ef1f867-20b4-48ea-aec1-bea7aff4964c" + ) + try { + MobileCredentialVerifier.initialize(context, platformConfiguration) + } catch (e: FailedToRegisterException) { + // Registration with the MATTR VII tenant failed — check connectivity and configuration + } catch (e: InvalidLicenseException) { + // The SDK license is missing, invalid, or expired + } ``` * `tenantHost`: The URL of your MATTR VII tenant where your verifier application is configured. * `applicationId`: The `id` of your configured MATTR VII verifier application. The majority of the SDK's other APIs can now also throw `InvalidLicenseException` when a valid license is not present. Network access is required the first time the SDK initializes (for registration) and when the license is renewed on subsequent initializations. Update your error handling, logging, analytics, and support diagnostics to account for these cases. ### Remove the `applicationId` argument from `requestMobileCredentials` [#remove-the-applicationid-argument-from-requestmobilecredentials] `MobileCredentialVerifier.requestMobileCredentials` (remote mobile, app-to-app) no longer takes an `applicationId` parameter. It now uses the application ID supplied in `PlatformConfiguration` during initialization. Remove the argument from your call sites: ```diff val result = verifier.requestMobileCredentials( request = listOf(mobileCredentialRequest), challenge = challenge, - applicationId = "your-application-id" ) ``` Ensure you provide `applicationId` via `PlatformConfiguration` (see the previous step) instead. ### Update revocation status list method and type names [#update-revocation-status-list-method-and-type-names] The revocation status list management API has been renamed from `TrustedIssuer`-prefixed terminology to `Revocation` terminology to better reflect its purpose — managing the lists used to check the revocation status of credentials. Update all call sites: ```diff - val result = verifier.updateTrustedIssuerStatusLists() + val result = verifier.refreshRevocationStatusLists() - val cacheInfo = verifier.getTrustedIssuerStatusListsCacheInfo() + val cacheInfo = verifier.getRevocationStatusListsCacheInfo() ``` | Old | New | | ---------------------------------------- | ------------------------------------- | | `updateTrustedIssuerStatusLists()` | `refreshRevocationStatusLists()` | | `getTrustedIssuerStatusListsCacheInfo()` | `getRevocationStatusListsCacheInfo()` | | `TrustedIssuerStatusListsCacheInfo` | `RevocationStatusListsCacheInfo` | | `UpdateTrustedIssuerStatusListsResult` | `RevocationStatusListsRefreshResult` | ### Update revocation status list refresh result handling [#update-revocation-status-list-refresh-result-handling] `RevocationStatusListsRefreshResult` (formerly `UpdateTrustedIssuerStatusListsResult`) has been converted from a data class to a sealed interface with `Success` and `Failure` variants, and the `.success: Boolean` field has been removed. Replace boolean branching with `when`/`is` pattern matching: ```diff - val result = verifier.refreshRevocationStatusLists() - if (result.success) { - // Handle success - } else { - // Handle failure - } + when (val result = verifier.refreshRevocationStatusLists()) { + is RevocationStatusListsRefreshResult.Success -> { + // Handle successful refresh + } + is RevocationStatusListsRefreshResult.Failure -> { + // result.failedLists is available here + } + } ``` ### Update online presentation session result handling [#update-online-presentation-session-result-handling] `OnlinePresentationSessionResult` has been converted from a data class to a sealed interface with `Success` and `Failure` variants. Replace field-based branching with `when`/`is` pattern matching: ```diff - val result = ... // OnlinePresentationSessionResult - if (result.mobileCredentialResponse != null) { - // Use result.mobileCredentialResponse - } else { - // Use result.error - } + when (result) { + is OnlinePresentationSessionResult.Success -> { + // result.mobileCredentialResponse is guaranteed + } + is OnlinePresentationSessionResult.Failure -> { + // result.error is guaranteed + } + } ``` ### Remove optional handling on `MobileCredentialResponse` collections [#remove-optional-handling-on-mobilecredentialresponse-collections] `MobileCredentialResponse.credentials` and `MobileCredentialResponse.credentialErrors` are now required non-nullable `List` fields, instead of nullable fields. Remove null checks and `?.` navigation, and provide both arguments when constructing the type: ```diff - for (credential in response.credentials.orEmpty()) { ... } + for (credential in response.credentials) { ... } ``` # iOS Verifier SDK v5.0.0 Migration Guide URL: /docs/verification/remote-mobile-verifiers/sdks/ios-5.0.0-migration-guide ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in MobileCredentialVerifierSDK v5.0.0 for iOS, including breaking changes, new features, and migration steps. ## Key Features [#key-features] * **Lifecycle management improvements**: New `destroy()` method enables complete SDK reset, plus `deinitialize()` no longer requires `@MainActor` context for improved flexibility. * **Improved reliability in contactless flows**: Enhanced BLE performance delivers more consistent proximity credential exchanges and faster engagements. * **Enhanced storage reliability**: Fixed intermittent storage initialization errors during app launch by migrating key storage from `UserDefaults` to the Keychain with explicit availability checks. * **Better timeout handling**: Proximity presentation session timeouts are now correctly propagated to the caller instead of being silently ignored. For a detailed list of changes included in this release, refer to the [SDK Changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/changelog). ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v5.0.0 that require updates to your existing implementation: | # | Symbol | Change | Impact | | - | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------- | | 1 | `MobileCredentialVerifier.sendProximityPresentationRequest(request:skipStatusCheck:)` | Parameter renamed to `checkStatus:` with **inverted semantics** | All call sites using `skipStatusCheck:` must be updated. | | 2 | `MobileCredentialVerifier.deinitialize()` | No longer `@MainActor` | Can now be called from any actor context. | | 3 | `MobileCredentialVerifierError` | Introduced two new enum cases: `.storageInitializedInBackground`, `.sdkInitialized` | Exhaustive `switch` statements must add new cases. | ## New Additions [#new-additions] ### Methods [#methods] | Method | Purpose | | ----------- | ---------------------------------------------------------------------------------------------------------------------------- | | `destroy()` | Destroys SDK instance and deletes all certificates from storage. Throws `sdkInitialized` if called while SDK is initialized. | ### Enum Cases [#enum-cases] | Type | New Case | Description | | ------------------------------- | --------------------------------- | --------------------------------------------------------------------- | | `MobileCredentialVerifierError` | `.storageInitializedInBackground` | SDK initialized without keychain access (e.g., during app prewarming) | | `MobileCredentialVerifierError` | `.sdkInitialized` | Operation requires SDK to be deinitialized first | ## Deprecations [#deprecations] No new deprecations. ## Bug Fixes [#bug-fixes] * Fixed intermittent "unable to initialize storage" errors during app launch. The issue was caused by using `UserDefaults` for `keyId` storage, which does not guarantee persistence—particularly during iOS app prewarming when protected data may be unavailable. The SDK now stores the `keyId` in the Keychain and performs explicit availability checks before initialization, throwing a new `storageInitializedInBackground` error when the keychain is inaccessible. * Fixed an issue where `ProximityPresentationSession` timeouts were silently ignored instead of throwing an error. The SDK now correctly propagates timeout errors to the caller when the session times out waiting for a response. ## Minimum Requirements [#minimum-requirements] * iOS 15+ for core SDK functionality. ## Migration Steps [#migration-steps] ### Update `sendProximityPresentationRequest` calls [#update-sendproximitypresentationrequest-calls] The `skipStatusCheck` parameter has been renamed to `checkStatus` with inverted semantics. When `checkStatus` is `true` (default), the SDK will verify credential status. When `false`, it will skip status checking. Update all calls accordingly: ```diff - let response = try await verifier.sendProximityPresentationRequest(request: requests, skipStatusCheck: true) + let response = try await verifier.sendProximityPresentationRequest(request: requests, checkStatus: false) ``` | Old Parameter | New Parameter | Mapping | | ---------------------------------- | ----------------------------- | ------------------ | | `skipStatusCheck: false` (default) | `checkStatus: true` (default) | No change needed | | `skipStatusCheck: true` | `checkStatus: false` | Invert the boolean | ### Handle new error cases [#handle-new-error-cases] If you have exhaustive `switch` statements on `MobileCredentialVerifierError`, you must add handlers for the two new error cases: ```diff switch error { case .sdkNotInitialized: // Handle not initialized case .storageInitialization: // Handle storage error + case .storageInitializedInBackground: + // SDK initialized during app prewarming — prompt user to retry + case .sdkInitialized: + // Operation requires deinitialize() first // ... other cases } ``` **Error handling guidance:** * `.storageInitializedInBackground`: This error occurs when the SDK is initialized during iOS app prewarming, before the keychain is accessible. Listen for `UIApplication.protectedDataDidBecomeAvailableNotification` and re-initialize the SDK when keychain access becomes available. * `.sdkInitialized`: This error is thrown by `destroy()` when called while the SDK is still initialized. Call `deinitialize()` first, then retry the operation. ### Use `destroy()` for complete reset (optional) [#use-destroy-for-complete-reset-optional] If you need to completely reset SDK state and delete all stored certificates and credentials, you can now call the new `destroy()` method. This is optional and should be used with caution as it will remove all data: ```swift // Ensure SDK is deinitialized first await MobileCredentialVerifier.shared.deinitialize() // Then destroy all stored data try MobileCredentialVerifier.shared.destroy() // Re-initialize when needed try MobileCredentialVerifier.shared.initialize() ``` ### Handle `deinitialize()` actor context change [#handle-deinitialize-actor-context-change] The `deinitialize()` method is no longer restricted to `@MainActor`. You can now call it from any context without dispatching to the main actor: ```diff - await MainActor.run { - await MobileCredentialVerifier.shared.deinitialize() - } + await MobileCredentialVerifier.shared.deinitialize() ``` # iOS Verifier SDK v6.0.0 Migration Guide URL: /docs/verification/remote-mobile-verifiers/sdks/ios-6.0.0-migration-guide Description: A comprehensive guide to migrating to iOS Verifier SDK v6.0.0, covering breaking changes, new features, and step-by-step migration instructions. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in the iOS Verifier SDK v6.0.0, including breaking changes, new features, and migration steps. This release focuses on strengthening trust between your verifier application and your MATTR VII tenant, improving consistency across platforms, and making verification results more predictable. The headline change is **SDK Tethering**, which becomes **required** in this release: every SDK and app instance is now registered with, and licensed by, your MATTR VII tenant at initialization. Unlike the Holder SDK, where SDK Tethering is optional, **SDK Tethering is required for the Verifier SDK** from v6.0.0. This builds on an existing requirement — remote mobile (app-to-app) verification already required you to supply a `platformConfiguration` so the SDK could reach your MATTR VII tenant to handle the backend verification. That `platformConfiguration` is now mandatory for all initializations and additionally drives SDK Tethering. ## Key Features [#key-features] * **SDK Tethering (required)**: The iOS Verifier SDK is now tethered to a MATTR VII tenant, tying each SDK/app instance to your tenant. On first initialization the SDK registers the app instance with the tenant specified in `PlatformConfiguration` and obtains a license; on subsequent initializations the existing license is renewed automatically. This lets you view registered and active app instances directly from your tenant for operational insight, and establishes a remote management channel we expect to extend in future releases (for example, remote syncing of trusted issuer lists and eventing). The SDK uses [App Attest](https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity) during app registration when available on the device. Network access is required when registration or renewal is performed. * **Asynchronous initialization**: `initialize` is now asynchronous, aligning the SDK with modern Swift concurrency and the registration/licensing work performed during tethering. * **Cross-platform alignment**: Verification result types and the revocation status list API have been renamed and restructured to align with the Android Verifier SDK, minimizing divergence for teams maintaining cross-platform applications. * **Simpler challenge handling for remote verification**: The `challenge` parameter for remote mobile (app-to-app) verification is now optional; when omitted, the SDK generates a cryptographically secure challenge for you. * **Clearer connectivity error reporting**: Remote mobile verification now surfaces a dedicated `connectivityError` when the internet connection is lost mid-flow. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency, and improve overall reliability. ## Breaking Changes [#breaking-changes] This section outlines the breaking changes introduced in v6.0.0 that require updates to your existing implementation: | # | Change | Impact | | -- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 | SDK Tethering is now required: `platformConfiguration` is mandatory on `initialize`, which now also registers the app instance and obtains a license | Always supply a `platformConfiguration`. Handle the new `invalidLicense` and `failedToRegister` errors, which can be thrown by `initialize` and by most SDK APIs. | | 2 | `initialize` is now asynchronous | Add `await` to all call sites and call `initialize` from an asynchronous context. | | 3 | `MobileCredentialVerifierError.platformConfigurationInvalid` removed | Remove handling for this case; `fetchAppleWalletConfiguration(request:merchantId:)` no longer throws it. | | 4 | `VerificationResult` renamed to `MobileCredentialVerificationResult`, with `.reason` renamed to `.failureType` | Update all type references, rename `.reason` to `.failureType`, and remove use of `VerificationFailedReason`. | | 5 | `TrustedCertificateVerificationResult.reason` renamed to `.failureType` | Rename `.reason` to `.failureType` and remove use of `VerificationFailedReason`. | | 6 | `MobileCredentialVerificationFailureType` now serializes as a `{type, message}` object instead of a plain raw-value string | Update any storage or transport layer that persists or forwards these serialized values. | | 7 | `TrustedCertificateVerificationFailureType` now serializes as a `{type, message}` object instead of a plain raw-value string | Update any storage or transport layer that persists or forwards these serialized values. | | 8 | Revocation status list methods and types renamed from `TrustedIssuer`-prefixed terminology to `Revocation` terminology | Rename `updateTrustedIssuerStatusLists` → `refreshRevocationStatusLists`, `getTrustedIssuerStatusListsCacheInfo` → `getRevocationStatusListsCacheInfo`, and the corresponding return types. | | 9 | `RevocationStatusListsRefreshResult` and `OnlinePresentationSessionResult` converted from structs with optional properties to `@frozen` enums with `success` and `failure` cases | Replace property-based branching with `switch`/`case` pattern matching. | | 10 | `applicationId` parameter removed from `fetchAppleWalletConfiguration` and `requestMobileCredentials` | Remove the `applicationId` argument from these call sites; the SDK now uses the `applicationId` from `PlatformConfiguration`. | ## Migration Steps [#migration-steps] ### Create a verifier application on your MATTR VII tenant [#create-a-verifier-application-on-your-mattr-vii-tenant] SDK Tethering requires a verifier application configured on the MATTR VII tenant your SDK connects to. If you already use remote mobile (app-to-app) verification you will have created one; the same application is reused for tethering. If you have not, create one now. To register your iOS application, make a request to create a verifier application: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID" } ``` * `name`: A unique name to identify your verifier application. * `type`: Must be `ios` for an iOS application. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID. The response will include a unique `id` for your application. This is the `applicationId` you supply in `PlatformConfiguration` at initialization, which the SDK now uses for all flows (including remote mobile verification). ### Supply `platformConfiguration` and make `initialize` asynchronous [#supply-platformconfiguration-and-make-initialize-asynchronous] `initialize` is now asynchronous and `platformConfiguration` is required. Previously, `platformConfiguration` was optional and only used for remote mobile (app-to-app) verification flows; it now also drives SDK Tethering, registering the app instance with your MATTR VII tenant and obtaining a license on first initialization. Add `await`, call `initialize` from an asynchronous context, and always pass a `platformConfiguration`: ```diff - let platformConfiguration = PlatformConfiguration( - tenantHost: URL(string: "https://your-tenant.vii.mattr.global")! - ) - try MobileCredentialVerifier.shared.initialize(platformConfiguration: platformConfiguration) + let platformConfiguration = PlatformConfiguration( + tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, + applicationId: "1ef1f867-20b4-48ea-aec1-bea7aff4964c" + ) + try await MobileCredentialVerifier.shared.initialize(platformConfiguration: platformConfiguration) ``` * `tenantHost`: The URL of your MATTR VII tenant where your verifier application is configured. * `applicationId`: The `id` of your configured iOS Verifier Application. Network access is required the first time the SDK initializes (for registration) and when the license is renewed on subsequent initializations. ### Handle license and registration errors [#handle-license-and-registration-errors] Because tethering registers and licenses the SDK, `initialize` can now throw `MobileCredentialVerifierError.invalidLicense` and `MobileCredentialVerifierError.failedToRegister`. The majority of the SDK's other APIs can now also throw `invalidLicense` when a valid license is not present. Update your error handling, logging, analytics, and support diagnostics to account for these cases: ```diff do { try await MobileCredentialVerifier.shared.initialize(platformConfiguration: platformConfiguration) } catch { switch error { + case MobileCredentialVerifierError.failedToRegister: + // Registration with the MATTR VII tenant failed — check connectivity and configuration + case MobileCredentialVerifierError.invalidLicense: + // The SDK license is missing, invalid, or expired // ... other cases } } ``` The `MobileCredentialVerifierError.platformConfigurationInvalid` case has been removed and is no longer thrown by `fetchAppleWalletConfiguration(request:merchantId:)`. Remove any handling for it. ### Update `VerificationResult` to `MobileCredentialVerificationResult` [#update-verificationresult-to-mobilecredentialverificationresult] The `VerificationResult` type has been renamed to `MobileCredentialVerificationResult` and aligned structurally with Android. Its `reason` property has been renamed to `failureType`, typed directly as `MobileCredentialVerificationFailureType?` rather than the now-removed `VerificationFailedReason` wrapper. `MobileCredential.verificationResult` and `MobileCredentialPresentation.verificationResult` now return `MobileCredentialVerificationResult`: ```diff - let result: VerificationResult = credential.verificationResult + let result: MobileCredentialVerificationResult = credential.verificationResult - let failure = result.reason + let failure = result.failureType ``` Replace all references to `VerificationResult` with `MobileCredentialVerificationResult`, rename `.reason` to `.failureType`, and remove any usage of `VerificationFailedReason`. ### Rename `TrustedCertificateVerificationResult.reason` to `failureType` [#rename-trustedcertificateverificationresultreason-to-failuretype] The same `.reason` → `.failureType` rename applies to `TrustedCertificateVerificationResult`. Its `failureType` is now typed directly as `TrustedCertificateVerificationFailureType?` instead of the now-removed `VerificationFailedReason` wrapper: ```diff - let failure = trustedCertificateResult.reason + let failure = trustedCertificateResult.failureType ``` ### Update failure-type serialization handling [#update-failure-type-serialization-handling] `MobileCredentialVerificationFailureType` and `TrustedCertificateVerificationFailureType` now encode and decode as a `{type, message}` object instead of a plain raw-value string: ```diff - "TrustedIssuerCertificateNotFound" + {"type": "TrustedIssuerCertificateNotFound", "message": "Trusted issuer certificate not found"} ``` If you persist or forward the serialized value of either failure type, update your storage or transport layer to produce and consume the new format. ### Update revocation status list method and type names [#update-revocation-status-list-method-and-type-names] The revocation status list management API has been renamed from `TrustedIssuer`-prefixed terminology to `Revocation` terminology to better reflect its purpose — managing the lists used to check the revocation status of credentials. Update all call sites to use the new method names and return types: ```diff - let result = try await verifier.updateTrustedIssuerStatusLists() + let result = try await verifier.refreshRevocationStatusLists() - let cacheInfo = verifier.getTrustedIssuerStatusListsCacheInfo() + let cacheInfo = try verifier.getRevocationStatusListsCacheInfo() ``` | Old | New | | ---------------------------------------- | ------------------------------------- | | `updateTrustedIssuerStatusLists()` | `refreshRevocationStatusLists()` | | `getTrustedIssuerStatusListsCacheInfo()` | `getRevocationStatusListsCacheInfo()` | | `UpdateTrustedIssuerStatusListsResult` | `RevocationStatusListsRefreshResult` | | `TrustedIssuerStatusListsCacheInfo` | `RevocationStatusListsCacheInfo` | ### Update result-type handling for `@frozen` enums [#update-result-type-handling-for-frozen-enums] `RevocationStatusListsRefreshResult` and `OnlinePresentationSessionResult` have been converted from structs with optional properties to `@frozen` enums with `success` and `failure` cases. Replace property-based branching with `switch`/`case` pattern matching. `RevocationStatusListsRefreshResult.success` carries `nextUpdate: Date?`, and `.failure` carries `nextUpdate: Date?` and `failedLists: [String: [String]]`: ```diff - let result = try await verifier.refreshRevocationStatusLists() - if result.success { - // Handle success - } else { - // Handle failure - } + switch try await verifier.refreshRevocationStatusLists() { + case .success(let nextUpdate): + // All status lists refreshed; schedule the next refresh before nextUpdate + case .failure(let nextUpdate, let failedLists): + // failedLists holds the URIs that failed to refresh, keyed by trusted issuer certificate ID + } ``` `OnlinePresentationSessionResult.success` carries `sessionId: String`, `challenge: String?`, and `mobileCredentialResponse: MobileCredentialResponse?`; `.failure` carries `sessionId: String`, `challenge: String?`, and `error: OnlinePresentationResultError`: ```diff - if let response = result.mobileCredentialResponse { - // Use response - } else { - // Use result.error - } + switch result { + case .success(_, _, let mobileCredentialResponse): + // mobileCredentialResponse?.credentials holds the presented credentials + case .failure(_, _, let error): + // error.type and error.message describe the failure + } ``` ### Remove the `applicationId` argument from `fetchAppleWalletConfiguration` and `requestMobileCredentials` [#remove-the-applicationid-argument-from-fetchapplewalletconfiguration-and-requestmobilecredentials] The `applicationId` parameter has been removed from `fetchAppleWalletConfiguration` and `requestMobileCredentials`. The SDK now uses the `applicationId` supplied in `PlatformConfiguration` during initialization. Remove the argument from your call sites: ```diff let result = try await verifier.requestMobileCredentials( request: [mobileCredentialRequest], challenge: challenge, - applicationId: "your-application-id" ) ``` Ensure you provide `applicationId` via `PlatformConfiguration` (see the earlier step) instead. ### (Optional) Simplify challenge handling for remote mobile verification [#optional-simplify-challenge-handling-for-remote-mobile-verification] The `challenge` parameter in `requestMobileCredentials` for remote mobile (app-to-app) verification is now optional. When omitted or left blank, the SDK generates a cryptographically secure random 32-byte challenge automatically, so you no longer need to manage challenge generation yourself: ```diff - let result = try await verifier.requestMobileCredentials( - request: [mobileCredentialRequest], - challenge: UUID().uuidString - ) + let result = try await verifier.requestMobileCredentials( + request: [mobileCredentialRequest] + ) ``` This is an optional improvement; supplying your own `challenge` continues to work. ### (Optional) Handle connectivity errors in remote mobile verification [#optional-handle-connectivity-errors-in-remote-mobile-verification] `requestMobileCredentials` for remote mobile (app-to-app) verification now throws `MobileCredentialVerifierError.connectivityError` if the internet connection is lost during the flow. Handle this case to provide clear feedback and retry guidance to your users: ```diff do { let result = try await verifier.requestMobileCredentials(request: requests) } catch { + if case MobileCredentialVerifierError.connectivityError = error { + // Prompt the user to check their connection and retry + } } ``` # Verifier Mobile SDKs Overview URL: /docs/verification/remote-mobile-verifiers/sdks/overview ## Overview [#overview] The mDocs Verifier SDK are based on the [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html) and [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) standards, which establish an interoperable digital representation of mobile-based credentials such as mobile drivers licenses (mDL). However, these SDKs can extend the same technology and architecture to more than just mDLs, but rather any conforming mobile document ([mDoc](/docs/concepts/mdocs)) - a term defined in ISO/IEC 18013-5. The mDocs Verifier SDKs are available for React Native, iOS, and Android. They help developers add mDocs verification capabilities to their apps, allowing secure and privacy preserving verification of presented mDocs in various interoperable workflows. To get started with any of our mDocs Verifier SDKs, please [contact us](mailto:sales@mattr.global). ## SDK Capabilities [#sdk-capabilities] The mDocs Verifier SDKs offer tools to assist developers integrating the following capabilities into their applications: * Interface with an mDoc holder to request presentations of issued mDocs via: * Proximity verification: Verify an mDoc presented in-person as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Remote mobile app verification: Remotely verify an mDoc presented from a different app installed on the same mobile device (as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html)). * Manage a list of trusted issuer certificates which presented mDocs can be validated against. * Manage status lists which can be used to check mDocs' [revocation status](/docs/issuance/revocation/overview). * Interface with an mDoc holder to request presentations of issued mDocs via: * Proximity verification: Verify an mDoc presented in-person as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Remote mobile app verification: Remotely verify an mDoc presented from a different app installed on the same mobile device (as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html)). * Manage a list of trusted issuer certificates which presented mDocs can be validated against. * Manage status lists which can be used to check mDocs' [revocation status](/docs/issuance/revocation/overview). * Interface with an mDoc holder to request presentations of issued mDocs via: * Proximity verification: Verify an mDoc presented in-person as per [ISO/IEC 18013-5:2021](https://www.iso.org/standard/69084.html). * Manage a list of trusted issuer certificates which presented mDocs can be validated against. * Manage status lists which can be used to check mDocs' [revocation status](/docs/issuance/revocation/overview). ## Supported features [#supported-features] ### Supported ISO/IEC 18013-5 Features [#supported-isoiec-18013-5-features] Below is a summary of ISO/IEC 18013-5 features supported by the mDocs Verifier SDKs: | Feature | Supported options | Default | | :------------------------------ | :----------------------------------------------------------------- | :------------------------------- | | Device engagement | QR code | QR code | | Device retrieval data transport | BLE with either `mDocPeripheralServer` or `mDocCentralClient` mode | Determined by holder application | | Ephemeral session key curve | Any NIST P-\* keys | Determined by holder application | | IACA public key curves | P-256, P-384, P-521 | Determined by Issuer | | Device authentication mode | Digital Signature or ECDH-agreed MAC | Determined by holder application | | Feature | Supported options | Default | | :------------------------------ | :----------------------------------------------------------------- | :------------------------------- | | Device engagement | QR code and NFC | QR code | | Device retrieval data transport | BLE with either `mDocPeripheralServer` or `mDocCentralClient` mode | Determined by holder application | | Ephemeral session key curve | Any NIST P-\* keys | Determined by holder application | | IACA public key curves | P-256, P-384, P-521 | Determined by Issuer | | Device authentication mode | Digital Signature or ECDH-agreed MAC | Determined by holder application | | Feature | Supported options | Default | | :------------------------------ | :----------------------------------------------------------------- | :------------------------------- | | Device engagement | QR code | QR code | | Device retrieval data transport | BLE with either `mDocPeripheralServer` or `mDocCentralClient` mode | Determined by holder application | | Ephemeral session key curve | Any NIST P-\* keys | Determined by holder application | | IACA public key curves | P-256, P-384, P-521 | Determined by Issuer | | Device authentication mode | Digital Signature or ECDH-agreed MAC | Determined by holder application | ## System requirements [#system-requirements] The SDK is developed in the [Swift](https://developer.apple.com/swift/) programming language and is meant for integration into iOS applications developed in Swift and/or Objective-C. Specifically, it currently only supports applications developed in iOS 15 and above. This SDK is developed in the Kotlin programming language and is meant for integration into Android applications. It currently supports Android 7 (API level 24) and above. This SDK is meant for integration into React Native applications using React Native 0.78 and above. Supported operating systems are: * iOS 15 or higher. * Android 7 or higher. ## Dependencies [#dependencies] This section lists all dependencies for using mDocs Verifier SDKs. **Third party dependencies** * [CBORCoding](https://github.com/SomeRandomiOSDev/CBORCoding) (MIT license). * [swift-certificates](https://github.com/apple/swift-certificates.git) 1.7.0 (Apache-2.0 License). * [swift-asn1](https://github.com/apple/swift-asn1) 1.3.1 (Apache-2.0 License). **Apple frameworks** * [Security](https://developer.apple.com/documentation/security) * [CryptoKit](https://developer.apple.com/documentation/cryptokit/) * [LocalAuthentication](https://developer.apple.com/documentation/localauthentication) * [CoreBluetooth](https://developer.apple.com/documentation/corebluetooth) * [Combine](https://developer.apple.com/documentation/combine) * [OSLog](https://developer.apple.com/documentation/oslog) * [UIKit](https://developer.apple.com/documentation/uikit) * [AppKit (on macOS)](https://developer.apple.com/documentation/appkit) * [PassKit](https://developer.apple.com/documentation/passkit) * [SwiftUI](https://developer.apple.com/documentation/swiftui) **Toolchain dependencies** * Swift 5.10. * iOS support: The SDK functionality is only available for devices from iOS 15 onwards. * Xcode: The SDK is built with the latest stable Xcode available in GitHub Actions (setup-xcode action with `latest-stable` label). The current version is Xcode 26.0.0 (17A324). * macOS 15. **Kotlin, AGP, Gradle, and Android Studio** The Android Verifier SDK is built using Kotlin 2.0. This adds some intrinsic dependencies into your build tools. * Kotlin 2.0 is supported from AGP version [8.5](https://developer.android.com/build/kotlin-support). * AGP 8.5 is supported from Gradle version [8.7](https://developer.android.com/build/releases/about-agp) and Android Studio Koala [2024.1.1](https://developer.android.com/studio/releases). **Runtime** A list of runtime dependencies and licenses is generated at build time and packaged in `res/raw/dependencies_licenses.html`. The majority are Kotlin and Android, the rest are listed below: * [CBOR-Java](https://github.com/peteroupc/CBOR-Java) - [Public Domain](https://github.com/peteroupc/CBOR-Java/blob/master/LICENSE.md) * [Timber](https://github.com/JakeWharton/timber) - [Apache 2.0](https://github.com/JakeWharton/timber/blob/trunk/LICENSE.txt) **Standard libraries** * [androidx.activity:activity-ktx:1.9.0](https://developer.android.com/jetpack/androidx/releases/activity#1.9.0) * [androidx.annotation:annotation:1.8.1](https://developer.android.com/jetpack/androidx/releases/annotation#1.8.1) * [androidx.appcompat:appcompat:1.7.0](https://developer.android.com/jetpack/androidx/releases/appcompat#1.7.0) * [androidx.biometric:biometric-ktx:1.2.0-alpha05](https://developer.android.com/jetpack/androidx/releases/biometric#1.2.0-alpha05) * [androidx.browser:browser:1.8.0](https://developer.android.com/jetpack/androidx/releases/browser#1.8.0) * [androidx.core:core-ktx:1.15.0](https://developer.android.com/jetpack/androidx/releases/core#1.15.0) * [androidx.credentials:credentials-play-services-auth:1.5.0](https://developer.android.com/jetpack/androidx/releases/credentials#1.5.0) * [androidx.credentials:credentials:1.5.0](https://developer.android.com/jetpack/androidx/releases/credentials#1.5.0) * [androidx.fragment:fragment:1.5.7](https://developer.android.com/jetpack/androidx/releases/fragment#1.5.7) * [org.jetbrains.kotlin:kotlin-reflect:1.9.22](https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect/1.9.22) * [org.jetbrains.kotlin:kotlin-stdlib:2.0.0](https://kotlinlang.org/) * [org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.3) * [org.jetbrains.kotlinx:kotlinx-datetime:0.4.0](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-datetime-jvm/0.4.0) * [org.jetbrains.kotlinx:kotlinx-io-bytestring:0.6.0](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-io-bytestring-tvosarm64/0.6.0) * [org.jetbrains.kotlinx:kotlinx-io-core:0.6.0](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-io-core/0.6.0) * [org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-serialization-json/1.6.3) **Third-party libraries** * [com.jakewharton.timber:timber:5.0.1](https://mvnrepository.com/artifact/com.jakewharton.timber/timber/5.0.1) * [com.upokecenter:cbor:4.5.2](https://mvnrepository.com/artifact/com.upokecenter/cbor/4.5.2) * None. ## Versions [#versions] Below are the available versions of the mDocs Verifier Mobile SDKs, including the current active version, supported versions, and those that have reached end-of-life (EOL). | Major version | Status | Latest release | End of Life date | Documentation | | ------------- | ----------- | -------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | v6 | Active | 6.0.0 | - | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/6.0.0/documentation/mobilecredentialverifiersdk/) | | v5 | Maintenance | 5.1.1 | 26-09-2026 | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/5.1.1/documentation/mobilecredentialverifiersdk/) | | v4 | End of Life | 4.1.0 | 13-05-2026 | - | | v3 | End of Life | 3.0.0 | 7-10-2025 | - | | v2 | End of Life | 2.0.0 | 26-08-2025 | - | | v1 | End of Life | 1.0.1 | 05-05-2025 | - | | Major version | Status | Latest release | End of Life date | Documentation | | ------------- | ----------- | -------------- | ---------------- | -------------------------------------------------------------------------------------------- | | v7 | Active | 7.0.0 | - | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/7.0.0/) | | v6 | Maintenance | 6.1.1 | 26-09-2026 | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/6.1.1/) | | v5 | End of Life | 5.3.2 | 13-05-2026 | - | | v4 | End of Life | 4.1.1 | 30-12-2025 | - | | v3 | End of Life | 3.0.0 | 7-10-2025 | - | | v2 | End of Life | 2.0.0 | 26-08-2025 | - | | v1 | End of Life | 1.0.1 | 05-05-2025 | - | | Major version | Status | Latest release | End of Life date | Documentation | | ------------- | ----------- | -------------- | ---------------- | ----------------------------------------------------------------------------------------------------------- | | v9 | Active | 9.0.3 | - | [SDK Docs](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/9.0.3/index.html) | | v8 | End of Life | 8.1.1 | 23-06-2026 | - | | v7 | End of Life | 7.1.0 | 12-12-2025 | - | | v6 | End of Life | 6.0.0 | 26-08-2025 | - | | v5 | End of Life | 5.0.0 | 05-05-2025 | - | | v4 | End of Life | 4.1.1 | 13-04-2025 | - | | v3 | End of Life | 3.0.0 | 15-02-2025 | - | | v2 | End of Life | 2.0.0 | 17-12-2024 | - | | v1 | End of Life | 1.0.1 | 05-07-2024 | - | Release candidates (RC) are pre-release versions that may contain new features or changes that are not yet fully tested. They are intended for testing purposes, are not subject to our SLA and should not be used in production environments. # React Native Verifier SDK v9.0.0 Migration Guide URL: /docs/verification/remote-mobile-verifiers/sdks/react-native-9.0.0-migration-guide Description: A guide to help developers migrate from React Native Verifier SDK v8.x to v9.0.0, including breaking changes, new features, and best practices. ## Overview [#overview] This guide provides a comprehensive overview of the changes introduced in React Native Verifier SDK v9.0.0, including breaking changes, new features, and migration steps. ## Key Features [#key-features] * **App to app verification**: The React Native Verifier SDK can now be used to request credentials from another app on the same device using OID4VP. This allows you to build verification flows directly into your apps and have a holder app on the same device respond. * **Verify with Apple Wallet (iOS Only)**: The SDK can now request and verify a credential directly from an Apple Wallet using the Verify with Wallet API. Only available for iOS 16 and above. * **Improved reliability in contactless flows**: Enhanced BLE performance delivers more consistent proximity credential exchanges and faster engagements. * **Status Lists Draft 14 Support**: The SDK now supports the Token Status List Draft 14 specification while maintaining existing support for Draft 3. * **Stronger cryptography and standards alignment**: Updated COSE algorithms (as per RFC 9864) strengthen cryptographic compatibility and ensure continued compliance with evolving standards. * **General stability and performance improvements**: Multiple refinements reduce integration friction, increase consistency across mobile environments, and improve overall user experience. For a detailed list of changes included in this release, refer to the [SDK Changelog](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/index.html#md:change-log). ## Breaking Changes [#breaking-changes] | # | Element | Change | Impact | | - | ------------------------------------------------------ | ------------------------------------------------------------------------------- | ---------------------------------------------------------- | | 1 | `initialize()` | Now accepts options and returns a `Result` type. | All call sites must handle the result and possible errors. | | 2 | `sendProximityPresentationRequest` | Option renamed from `skipStatusCheck` to `checkStatus` with inverted semantics. | All call sites using `skipStatusCheck` must be updated. | | 3 | `ProximityPresentationSessionTerminationErrorType` | New `Exception` value added. | Exhaustive switches must handle new case. | | 4 | NFC error listener in `registerForNfcDeviceEngagement` | Now routes parse failures to `onError` instead of rethrowing. | Update error handling logic accordingly. | ## New Additions [#new-additions] ### Functions [#functions] | Function | Description | Platform | | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | | `fetchAppleWalletConfiguration(options)` | Fetches Apple Wallet configuration needed before initiating the Apple Wallet verification flow. Returns an `AppleWallet` object. | iOS only | | `handleDeepLink(options)` | Handles a deep link URL to continue the online presentation flow. | iOS only | | `requestMobileCredentials(options)` | Remote app-to-app credential verification via Digital Credential Manager / OID4VP. Returns `OnlinePresentationSessionResult`. | Android (DCM/OID4VP), iOS (OID4VP only) | | `destroy()` | Destroys the verifier SDK instance. Returns an error if called while the SDK is initialized; deinitialize the SDK before calling. | All | | `getCurrentLogFilePath()` | Returns path to the Verifier SDK log file. | All | ### Updated Function Signatures [#updated-function-signatures] * `initialize(options?)` — new `InitializeOptions` parameter: * `loggerConfiguration?: LoggerConfiguration` — `logLevel`, `callbackLogLevel`, optional `logDir`, optional `callback` * `platformConfiguration?: PlatformConfiguration` — `tenantHost: string` (required for `requestMobileCredentials`) ### New initialize Options [#new-initialize-options] * `loggerConfiguration?: LoggerConfiguration` — configure SDK logging: logLevel, callbackLogLevel, logDir, callback on log events. * `platformConfiguration?: PlatformConfiguration` — set MATTR VII tenant host for remote credential requests. ### New Types & Enums [#new-types--enums] * **Online presentation (remote/app-to-app):** * `OnlinePresentationSessionResult` — `{ sessionId, challenge?, mobileCredentialResponse?, error? }` * `OnlinePresentationResultError` — `{ type: OnlinePresentationResultErrorType, message }` * `OnlinePresentationResultErrorType` — `SessionAborted | VerificationError | ResponseError | WalletUnavailable | Unknown` * **Apple Wallet:** * `AppleWallet` — object with `requestMobileCredentials(challenge): Promise>` * `RequestMobileCredentialsWithAppleWalletError` / `RequestMobileCredentialsWithAppleWalletErrorType` * **`requestMobileCredentials` options & errors:** * `RequestMobileCredentialsOptions` — `{ request, applicationId, walletProviderId?, challenge }` * `RequestMobileCredentialsErrorType` * `RequestMobileCredentialsError` * **`handleDeepLink`:** * `HandleDeepLinkOptions` — `{ url: string }` * **`fetchAppleWalletConfiguration`:** * `FetchAppleWalletConfigurationOptions` — `{ request, applicationId, merchantId }` * `FetchAppleWalletConfigurationError` / `FetchAppleWalletConfigurationErrorType` * **`initialize`:** * `InitializeOptions`, `LoggerConfiguration`, `PlatformConfiguration`, `LogLevel` (`Off | Error | Warn | Info | Debug | Verbose`) * `InitializeErrorType` = `SdkInitialized | StorageInitializedInBackground` ### New Error Values [#new-error-values] | Location | New values | | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MobileCredentialVerifierErrorType` | `Connectivity`, `StorageInitializedInBackground`, `SdkInitialized`, `PlatformNotSupported`, `PlatformConfigurationInvalid`, `SessionTimedOut`, `SessionAborted`, `DigitalCredentialManager`, `UserCanceled`, `FailedToRequestMobileCredentials`, `AppleWalletNotAvailable` | | `ProximityPresentationSessionTerminationErrorType` | `Exception` | ## Minimum Requirements [#minimum-requirements] **iOS** * iOS 15+ for core SDK functionality. **Android** * Android 7 / Nougat / API 24. * The underlying Android Verifier SDK is built using Kotlin 2.0. This adds some intrinsic dependencies into your build tools. * Kotlin 2.0 is supported from AGP version [8.5](https://developer.android.com/build/kotlin-support). * AGP 8.5 is supported from Gradle version [8.7](https://developer.android.com/build/releases/about-agp) and Android Studio Koala [2024.1.1](https://developer.android.com/studio/releases). ## Migration Steps [#migration-steps] ### Update `initialize()` usage [#update-initialize-usage] The `initialize()` function now accepts an optional options object and returns a `Result`. Update your code to handle the result and possible errors: ```diff - await initialize(); + const result = await initialize(options); + if (result.isErr()) { + // Handle error: result.error + } ``` ### Update `sendProximityPresentationRequest` calls [#update-sendproximitypresentationrequest-calls] The `skipStatusCheck` parameter has been renamed to `checkStatus` with inverted semantics. When `checkStatus` is `true` (default), the SDK will verify credential status. When `false`, it will skip status checking. Update all calls accordingly: ```diff - const response = await sendProximityPresentationRequest({ ..., skipStatusCheck: true }); + const response = await sendProximityPresentationRequest({ ..., checkStatus: false }); ``` | Old Parameter | New Parameter | Mapping | | ----------------------------------- | ------------------------------ | ------------------ | | `skipStatusCheck = false` (default) | `checkStatus = true` (default) | No change needed | | `skipStatusCheck = true` | `checkStatus = false` | Invert the boolean | ### Handle new `ProximityPresentationSessionTerminationErrorType.Exception` case [#handle-new-proximitypresentationsessionterminationerrortypeexception-case] If you switch over `ProximityPresentationSessionTerminationErrorType`, add handling for the new `Exception` value. ### Update NFC error handling [#update-nfc-error-handling] The NFC error listener in `registerForNfcDeviceEngagement` now routes parse failures to `onError` instead of rethrowing. Update your error handling logic accordingly. # Getting started with the Verifier SDKs URL: /docs/verification/remote-mobile-verifiers/sdks/sdk-getting-started Description: Set up access to the MATTR Pi mDocs Verifier SDKs, configure SDK tethering, and initialize the SDK in your mobile application. This guide walks you through the steps required to start building with the MATTR Pi mDocs Verifier SDKs. By the end, your mobile application will be ready to verify credential presentations. For the native iOS and Android Verifier SDKs, this includes tethering your application to a MATTR VII tenant. The React Native Verifier SDK is not tethered: for in-person (proximity) verification it does not require a MATTR VII tenant or platform configuration, and only needs a tenant for remote mobile (app-to-app) verification. React Native differences are called out at each step below. ### Request SDK access [#request-sdk-access] To access the MATTR Pi mDocs Verifier SDKs, complete the [Get Started form](/docs/resources/get-started) with the following details: * Your organization name and contact information. * The platform(s) you plan to build for (iOS, Android, or React Native). * A brief description of your use case. ### Create a MATTR VII tenant [#create-a-mattr-vii-tenant] The native iOS and Android Verifier SDKs require a MATTR VII tenant that serves as the backend for SDK operations including tethering and credential verification. For React Native, a tenant is only required for remote mobile (app-to-app) verification. If you are building React Native for in-person verification only, you can skip this step. 1. Log into the [MATTR Portal](https://portal.mattr.global). 2. Select the **Create/switch tenant** button on the top-right side of the screen.\ The *All tenants* panel is displayed, listing any existing tenants. 3. Select the **Create new** button.\ The *New tenant* form is displayed. 4. Use the *Region* dropdown list to select the region your tenant will be hosted in. 5. Use the *Tenant subdomain* text box to insert a subdomain for your tenant (e.g. `in-person-verification`). 6. Select the **Create** button to create the new tenant. 7. Copy the displayed tenant information (`audience`, `auth_url`, `tenant_url`, `client_id` and `client_secret`). ### Create a Verifier Application [#create-a-verifier-application] The iOS and Android Verifier SDKs are **tethered** to a MATTR VII tenant. On initialization, the SDK registers your app instance with the tenant and obtains a license, so SDK Tethering must be configured before you initialize the SDK. For a full explanation of tethering and the capabilities it enables, see [SDK Tethering](/docs/verification/sdks/sdk-tethering). To tether the SDK, create a Verifier Application on your MATTR VII tenant for the platform you are building. Make a request of the following structure to create an iOS Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `ios`. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID (must match the Team ID used to sign your app). * `appAttest`: App Attest configuration for the iOS verifier application: * `required`: When `true`, the app instance must provide a valid App Attest attestation during registration and token renewal. When `false`, the app can fall back to assertion-only authentication. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `environment`: The App Attest environment (`development` or `production`). Apple recommends using `development` for testing and `production` for distribution builds. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", // [!code focus] "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. Make a request of the following structure to create an Android Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false }, "openid4vpConfiguration": { "redirectUri": "com.yourcompany.verifierapp://oid4vp-callback" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `android`. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/verification/android-app-signing) for more information. * `keyAttestation`: Key Attestation configuration for the Android verifier application: * `required`: When `true`, the app instance must provide a valid Key Attestation during registration and token renewal. When `false`, the app can register and renew tokens using just an authentication assertion. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `openid4vpConfiguration.redirectUri`: Required by the create-application endpoint, which needs at least one of `openid4vpConfiguration` or `dcApiConfiguration`. In-person proximity verification does not use this redirect, so any valid custom-scheme URI is accepted here. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", // [!code focus] "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. SDK Tethering is currently not required for the React Native Verifier SDK. ### Initialize the SDK [#initialize-the-sdk] When you initialize the SDK, you must provide a `PlatformConfiguration` object with your tenant host and the `id` of the Verifier Application you created. This allows the SDK to register the app instance with your tenant and obtain a license to operate. Initialize the SDK with your platform configuration. The `initialize` method is asynchronous, so call it from an asynchronous context: ```swift title="Initialization" let platformConfig = PlatformConfiguration( tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, applicationId: "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) try await MobileCredentialVerifier.shared.initialize( platformConfiguration: platformConfig ) ``` * `tenantHost`: The URL of your MATTR VII tenant. This must be the tenant where your iOS Verifier Application is configured. * `applicationId`: The `id` of your configured iOS Verifier Application. Initialize the SDK with your platform configuration: ```kotlin title="Initialization" val platformConfig = PlatformConfiguration( tenantHost = URL("https://your-tenant.vii.mattr.global"), applicationId = "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) MobileCredentialVerifier.initialize(context, platformConfig) ``` * `tenantHost`: The URL of your MATTR VII tenant where your Android Verifier Application is configured. * `applicationId`: The `id` of your configured Android Verifier Application. The React Native Verifier SDK is **not** tethered, so initialization does not register an app instance or require a `platformConfiguration` object. ## Next steps [#next-steps] Your application is now initialized and tethered to your MATTR VII tenant, ready to verify credentials. Explore the following resources to start building: * In-person verification: * [Quickstart](/docs/verification/in-person-quickstart): Run a sample in-person verifier app end-to-end. * [Tutorial](/docs/verification/in-person-tutorial): Detailed walkthrough of building an app that can verify credentials in-person using Bluetooth proximity presentations. * Remote mobile verification: * [Quickstart](/docs/verification/remote-mobile-verifiers/quickstart): Run a sample remote mobile verifier app end-to-end. * [Tutorial](/docs/verification/remote-mobile-verifiers/tutorial): Detailed walkthrough of building an app that can request and verify credentials from a wallet app on the same device (app-to-app). # Configure SDK logging URL: /docs/verification/remote-mobile-verifiers/sdks/sdk-logging Description: Learn how to configure logging in the MATTR Verifier SDKs, including log levels, callback handlers, and accessing log files across iOS, Android, and React Native platforms. The MATTR Verifier SDKs include a built-in logging system that records internal SDK operations. This is useful for debugging integration issues, monitoring SDK behavior, and capturing diagnostic information during development and testing. By default, SDK logs are stored on the device. The SDK itself does not transmit logs to any external service, although your application can choose to forward log events elsewhere if you register a callback. ## What information the SDK can log [#what-information-the-sdk-can-log] The SDK can log information about its internal operations, including errors and warnings encountered during SDK operations. All log entries include the log level and a descriptive message. This information helps you diagnose issues and understand how the SDK operates within your application. ## Log levels [#log-levels] The SDK supports the following log levels, ordered from most to least verbose: | Level | Description | | --------- | -------------------------------------------------------------------------- | | `Verbose` | Fine-grained informational events, most detailed output. | | `Debug` | Detailed information useful during development. | | `Info` | General informational messages about SDK operations. | | `Warning` | Potentially harmful situations or unexpected behavior (`Warn` in Android). | | `Error` | Error events that might still allow the SDK to continue running. | | `Assert` | Severe error events that indicate a critical failure (Android only). | | `Off` | Disables logging entirely. | The SDK uses the configured log level as a threshold: it records log events at the specified level and any less verbose levels. For example, setting the level to `Info` captures `Info`, `Warning`, `Error`, and `Assert` events, but not `Debug` or `Verbose`. ## Configure logging at initialization [#configure-logging-at-initialization] You can configure logging behavior by passing a `loggerConfiguration` object to the SDK's `initialize` method. This configuration accepts two separate log levels: * **`logLevel`**: Controls which log events are written to the log file. * **`callbackLogLevel`**: Controls which log events trigger the optional callback function. ```swift title="Configure logging during initialization" try mobileCredentialVerifier.initialize( loggerConfiguration: LoggerConfiguration( // [!code highlight] logLevel: .Info, // [!code highlight] callbackLogLevel: .Warning // [!code highlight] ) // [!code highlight] ) ``` * `logLevel`: Sets the minimum level for writing log entries to the log file. Defaults to `.Off`. * `callbackLogLevel`: Sets the minimum level for invoking the callback closure. Defaults to `.Off`. Refer to the [`LoggerConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/loggerconfiguration) reference documentation for additional details. ```kotlin title="Configure logging during initialization" mobileCredentialVerifier.initialize( loggerConfiguration = Logger.LoggerConfiguration( logLevel = Logger.LogLevel.INFO, callbackLogLevel = Logger.LogLevel.WARN ), // ... ) ``` * `logLevel`: Sets the minimum level for writing log entries to the log file and Logcat. Defaults to `Logger.LogLevel.OFF`. * `callbackLogLevel`: Sets the minimum level for invoking the callback function. Defaults to `Logger.LogLevel.OFF`. * `logDir`: Optional. Specifies a local directory to store log files. If not provided, logs won't be stored to file. Refer to the [`Logger.LoggerConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier.util/-logger/-logger-configuration/index.html) reference documentation for additional details. ```ts title="Configure logging during initialization" import { LogLevel } from "@mattrglobal/mobile-credential-verifier-react-native" const initializeResult = await mobileCredentialVerifier.initialize({ loggerConfiguration: { // [!code highlight] logLevel: LogLevel.Info, // [!code highlight] callbackLogLevel: LogLevel.Warn, // [!code highlight] }, // [!code highlight] }) if (initializeResult.isErr()) { const { error } = initializeResult // handle error scenarios return } ``` * `logLevel`: Sets the minimum level for writing log entries to the log file and console. Defaults to `LogLevel.Off`. * `callbackLogLevel`: Sets the minimum level for invoking the callback function. Defaults to `LogLevel.Off`. * `logDir`: Optional. Specifies a directory to store log files. On iOS, a default directory is used. On Android, logs won't be stored to file if not provided. Refer to the [`LoggerConfiguration`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/types/LoggerConfiguration.html) reference documentation for additional details. ## Handle log events with a callback [#handle-log-events-with-a-callback] You can register a callback function during initialization to receive log events in real time. This allows your application to process log events as they occur, for example to forward them to a custom logging service, display them in a debug console, or filter specific events for monitoring. The callback is only invoked for log events at or above the `callbackLogLevel` threshold. ```swift title="Register a logging callback" try mobileCredentialVerifier.initialize( loggerConfiguration: LoggerConfiguration( logLevel: .Info, callbackLogLevel: .Warning, callback: { logEvent in // [!code highlight] print("[\(logEvent.level)] \(logEvent.message)") // [!code highlight] } // [!code highlight] ) ) ``` The callback receives a [`LogEvent`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/logevent) object with the following properties: * `level`: The [`LogLevel`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/loglevel) of the event. * `message`: A string describing the log event. ```kotlin title="Register a logging callback" mobileCredentialVerifier.initialize( loggerConfiguration = Logger.LoggerConfiguration( logLevel = Logger.LogLevel.INFO, callbackLogLevel = Logger.LogLevel.WARN, callback = { priority, tag, message, throwable -> // [!code highlight] Log.d("VerifierSDK", "[$tag] $message") // [!code highlight] } // [!code highlight] ), // ... ) ``` The callback function receives the following parameters: * `priority`: An integer representing the log priority level. * `tag`: An optional string tag identifying the log source. * `message`: A string describing the log event. * `throwable`: An optional `Throwable` associated with the log event (for error-level logs). ```ts title="Register a logging callback" import { LogLevel } from "@mattrglobal/mobile-credential-verifier-react-native" const initializeResult = await mobileCredentialVerifier.initialize({ loggerConfiguration: { logLevel: LogLevel.Info, callbackLogLevel: LogLevel.Warn, callback: (log) => { // [!code highlight] console.log(`[${log.logLevel}] ${log.tag ?? ""}: ${log.message ?? ""}`) // [!code highlight] }, // [!code highlight] }, }) if (initializeResult.isErr()) { const { error } = initializeResult // handle error scenarios return } ``` The callback receives a log object with the following properties: * `logLevel`: The [`LogLevel`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/enums/LogLevel.html) of the event. * `message`: An optional string describing the log event. * `tag`: An optional string tag identifying the log source. ## Access the log file [#access-the-log-file] The SDK writes log entries to a file that you can access for debugging and diagnostics. The log file contains entries from the previous two calendar days. ```swift title="Get the log file path" let logFilePath = mobileCredentialVerifier.getCurrentLogFilePath() ``` The [`getCurrentLogFilePath`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-ios/latest/documentation/mobilecredentialverifiersdk/mobilecredentialverifier/getcurrentlogfilepath\(\)) method returns the file path as a string, or `nil` if no log file is available. To read the logs, use the returned file path to load the file contents into `Data`, then decode the data into text and split it into individual log messages as needed. ```kotlin title="Get the log file path" val logFilePath = mobileCredentialVerifier.getLog() ``` The [`getLog`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-android/latest/m-docs%20-verifier%20-s-d-k/global.mattr.mobilecredential.verifier/-mobile-credential-verifier/get-log.html) method returns the full path of the log file (with a `.log` extension), or `null` if no log file is available. The file contains log entries from the previous two calendar days. ```ts title="Get the log file path" const logFilePath = await mobileCredentialVerifier.getCurrentLogFilePath() ``` The [`getCurrentLogFilePath`](https://api-reference-sdk.mattr.global/mobile-credential-verifier-react-native/latest/functions/getCurrentLogFilePath.html) method returns a `Promise` resolving to the log file path string, or `null` if no log file is available. # SDK Tethering URL: /docs/verification/remote-mobile-verifiers/sdks/sdk-tethering Description: Learn how MATTR Verifier SDKs are tethered to a MATTR VII tenant through Verifier Application configuration, enabling operational insights and licensing. SDK Tethering ties each SDK/app instance to a MATTR VII tenant. Tethering establishes a trust relationship between your mobile application and the MATTR VII tenant, enabling the following capabilities: * **Operational insights**: View details about registered and active app instances directly from your tenant. * **Licensing**: On first initialization, the SDK registers the app instance with your tenant and obtains a license. The majority of the SDK's APIs require a valid license to operate. * **Remote management channel**: SDK Tethering establishes a channel that we expect to extend in the future with capabilities such as remote syncing of trusted issuer lists and eventing. SDK Tethering is required from the following SDK versions: * **iOS Verifier SDK**: 6.0.0 * **Android Verifier SDK**: 7.0.0 ## How it works [#how-it-works] The tethering process involves three steps: 1. **Configure a Verifier Application on your MATTR VII tenant**: You register your mobile app by creating a Verifier Application, identified by the bundle identifier and team ID (iOS) or the package fingerprint (Android). 2. **Initialize the SDK with your tenant details**: When you initialize the SDK in your app, you pass the details of the MATTR VII tenant and the Verifier Application you configured on it. 3. **Automatic communication**: Once initialized, instances of your app will automatically communicate with the configured MATTR VII tenant and retrieve the required tokens to operate and make requests to the tenant when required. ## Token validity and offline use [#token-validity-and-offline-use] The tokens issued during this process have configurable validity periods controlled by the `maxTimeOfflineInSecs` field on your Verifier Application configuration. This means your app can function without internet connectivity to meet different use cases: * **Minimum**: 1 day (86400 seconds) * **Maximum**: 30 days (2592000 seconds) * **Default**: 7 days (604800 seconds) When the license token expires, the SDK must reconnect to the MATTR VII tenant to renew it. Network access is required when registration or renewal is performed. ## Configuring SDK Tethering [#configuring-sdk-tethering] ### Configure Verifier Applications [#configure-verifier-applications] Make a request of the following structure to create an iOS Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `ios`. * `bundleId`: The Bundle ID of your iOS app (must match your Xcode project configuration). * `teamId`: Your Apple Developer Team ID (must match the Team ID used to sign your app). * `appAttest`: App Attest configuration for the iOS verifier application: * `required`: When `true`, the app instance must provide a valid App Attest attestation during registration and token renewal. When `false`, the app can fall back to assertion-only authentication. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `environment`: The App Attest environment (`development` or `production`). Apple recommends using `development` for testing and `production` for distribution builds. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", // [!code focus] "name": "My iOS Verifier Application", "type": "ios", "bundleId": "com.yourcompany.verifierapp", "teamId": "YOUR_APPLE_TEAM_ID", "appAttest": { "required": false, "environment": "development" } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. Make a request of the following structure to create an Android Verifier Application configuration on your MATTR VII tenant: ```http title="Request" POST /v2/presentations/applications ``` ```json title="Request body" { "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false }, "openid4vpConfiguration": { "redirectUri": "com.yourcompany.verifierapp://oid4vp-callback" } } ``` * `name`: A unique name to identify this Verifier Application. * `type`: Must be `android`. * `packageName`: The package name of your Android application. * `packageSigningCertificateThumbprints`: SHA-256 hex-encoded fingerprints of the signing key certificates used to sign your APK or app bundle. This ensures the tenant only accepts requests from known and trusted applications. Refer to [Android app signing](/docs/verification/android-app-signing) for more information. * `keyAttestation`: Key Attestation configuration for the Android verifier application: * `required`: When `true`, the app instance must provide a valid Key Attestation during registration and token renewal. When `false`, the app can register and renew tokens using just an authentication assertion. See [Attestation vs Assertion](/docs/verification/sdks/sdk-tethering#attestation-vs-assertion-fall-back) for more details. * `openid4vpConfiguration.redirectUri`: Required by the create-application endpoint, which needs at least one of `openid4vpConfiguration` or `dcApiConfiguration`. In-person proximity verification does not use this redirect, so any valid custom-scheme URI is accepted here. A successful response returns a `201` status code with the created Verifier Application: ```json title="Response" { "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", // [!code focus] "name": "My Android Verifier Application", "type": "android", "packageName": "com.yourcompany.verifierapp", "packageSigningCertificateThumbprints": [ "1232584B6F6A892D356899FB9576C5F226A179E6199F2B7A1D837B5C234C5A8E" ], "keyAttestation": { "required": false } } ``` * `id`: A unique identifier for the Verifier Application (generated by the tenant). You must use this value when initializing the SDK so that it can correctly identify and authenticate your application. SDK Tethering is currently not required for the React Native Verifier SDK. ### Initialize the SDK with platform configuration [#initialize-the-sdk-with-platform-configuration] When you initialize the SDK, you must provide a `PlatformConfiguration` object with your tenant host and the `id` of the Verifier Application you created. This allows the SDK to register the app instance with your tenant and obtain a license to operate. Initialize the SDK with your platform configuration. The `initialize` method is asynchronous, so call it from an asynchronous context: ```swift title="Initialization" let platformConfig = PlatformConfiguration( tenantHost: URL(string: "https://your-tenant.vii.mattr.global")!, applicationId: "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) try await MobileCredentialVerifier.shared.initialize( platformConfiguration: platformConfig ) ``` * `tenantHost`: The URL of your MATTR VII tenant. This must be the tenant where your iOS Verifier Application is configured. * `applicationId`: The `id` of your configured iOS Verifier Application. Initialize the SDK with your platform configuration: ```kotlin title="Initialization" val platformConfig = PlatformConfiguration( tenantHost = URL("https://your-tenant.vii.mattr.global"), applicationId = "1ef1f867-20b4-48ea-aec1-bea7aff4964c" ) MobileCredentialVerifier.initialize(context, platformConfig) ``` * `tenantHost`: The URL of your MATTR VII tenant where your Android Verifier Application is configured. * `applicationId`: The `id` of your configured Android Verifier Application. SDK Tethering is currently not required for the React Native Verifier SDK. Once your Verifier Application configurations are created, your application will be able to use the SDK and interact with the MATTR VII platform (for example, to verify credential presentations). ## Managing application instances [#managing-application-instances] Once your Verifier Application is configured and the SDK is initialized, each device that launches your app registers as a new application instance on your tethered MATTR VII tenant. You can view and manage these instances via the MATTR VII API. ### Retrieve all registered instances [#retrieve-all-registered-instances] To view all registered instances for a Verifier Application and track usage: ```http title="Request" GET /v2/presentations/applications/{applicationId}/instances ``` * `applicationId` : The `id` of the Verifier Application you want to inspect. The response includes a paginated list of all registered instances: ```json title="Response" { "data": [ { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "appAttestationType": "app_attestation", "registeredAt": "2023-10-05T14:48:00.000Z", "licenseExpiresAt": "2024-10-05T14:48:00.000Z", "lastAttestedAt": "2023-12-01T10:30:00.000Z", "externalReferenceId": "external-ref-12345", "deviceDetails": { "deviceModel": "iPhone 12", "deviceMake": "Apple", "osVersion": "iOS 14.4" }, "sdkDetails": { "sdkVersion": "1.2.3" } } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` Each instance includes: * `id` : Unique identifier for the registered instance. * `appAttestationType` : The type of attestation used during registration (`none`, `app_attestation`, or `key_attestation`). * `registeredAt` : When the instance was first registered. * `licenseExpiresAt` : When the instance's license expires (the Verifier SDK will automatically handle license renewal). * `lastAttestedAt` : When the instance was last attested. * `deviceDetails` : Information about the device (model, make, OS version). * `sdkDetails` : Information about the SDK version used by the instance. This is useful for tracking how many devices are actively using your application and monitoring usage quotas. ### Delete a specific instance [#delete-a-specific-instance] To remove a specific registered instance: ```http title="Request" DELETE /v2/presentations/applications/{applicationId}/instances/{instanceId} ``` * `applicationId` : The `id` of the Verifier Application. * `instanceId` : The `id` of the specific instance to delete. Once deleted, the instance can no longer interact with the platform or receive tokens, and any existing tokens are revoked. Deleting instances is primarily useful during **testing** when you have a limited number of devices and need to re-register a fresh instance (for example, to test the initial registration flow again). In production, there is nothing preventing the application from requesting another token on the next launch, which would create a new instance — so deleting instances is not an effective way to block a device. ## Attestation vs Assertion fall-back [#attestation-vs-assertion-fall-back] When configuring a Verifier Application, you control whether your MATTR VII tenant requires **attestation** (hardware-backed proof of app integrity) or also accepts a lighter-weight **assertion** (a cryptographic signature proving key possession) during instance registration and token renewal. Each platform has an attestation configuration with a `required` boolean: * When `required` is `true`, the app instance must provide a valid attestation during registration and token renewal. * When `required` is `false`, your tenant also accepts an assertion when an attestation is not available. The SDK handles this automatically. It always attempts to provide an attestation, and falls back to an assertion if it cannot generate one (for example, when the platform attestation service is temporarily unavailable). Your tenant then accepts or rejects the request based on the `required` setting. Your application does not need to manage attestation or assertion details directly. ### When to use each setting [#when-to-use-each-setting] | Scenario | Recommended setting | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | **Production apps in distribution** | `required: true`: Provides the strongest integrity guarantees by verifying the app and device through OS-level attestation. | | **Development and testing** | `required: false`: Useful when running on simulators or devices where attestation services are unavailable. | | **Broad device compatibility** | `required: false`: Some older devices may not support hardware attestation. The assertion fall-back ensures these devices can still register. | Setting attestation to `required: false` reduces the security guarantees of the tethering process. Only use this setting when you have a specific need, such as supporting older devices or during development. # Build a web application that can request and verify credentials via the Digital Credentials API URL: /docs/verification/remote-web-verifiers/dc-api/guide Description: Learn how to use the Verifier Web SDK to verify credentials using the Digital Credentials API (DC API) in a web application. ## Overview [#overview] DC API support is currently offered as a tech preview. The Digital Credentials API specification itself is still under active development in the W3C Web Incubator CG, and platform implementations continue to evolve. As such, functionality may be limited, may not work in all scenarios, and could change or break without prior notice as browsers and operating systems update their implementations. This guide demonstrates how to use the [Verifier Web SDK](/docs/verification/remote-web-verifiers/sdks/overview) to verify credentials using the [Digital Credentials API](/docs/verification/remote-web-verifiers/dc-api/overview) (DC API). ## Prerequisites [#prerequisites] This guide builds on the [Verifier Web SDK tutorial](/docs/verification/remote-web-verifiers/tutorial). It is recommended to complete that tutorial first, then return here to add support for the [DC API workflow](/docs/verification/remote-web-verifiers/workflow#dc-api-workflow). You will also need: * Version `2.1.0` or later of the [Verifier Web SDK](https://www.npmjs.com/package/@mattrglobal/verifier-sdk-web). * A web browser that supports the Digital Credentials API: * Chrome or a Chromium based browser v138 or later. * Safari v26 or later. * A wallet application that supports the Digital Credentials API for testing: * Google Wallet with a compliant credential, or Google's developer wallet with a test credential. * Apple Wallet (requires iOS 26 on an iPhone 11 or later) with a compliant credential or a [Wallet Identity Developer profile](https://developer.apple.com/bug-reporting/profiles-and-logs/?name=wallet\&platform=ios) installed. * MATTR Labs wallet, available to selected MATTR customers/partners. ## Adjusting your web application to use the DC API [#adjusting-your-web-application-to-use-the-dc-api] Most of the Verifier Web SDK integration remains the same when using the DC API. The Verifier Web SDK automatically uses the DC API when **all** of the following conditions are met: 1. The MATTR VII verifier application configuration has DC API support enabled. 2. The user's browser supports the DC API. 3. The credential request is for a **single credential** (DC API doesn't support multiple credentials in one request). 4. No `walletProviderId` is provided in the request options. If any condition is not met, the SDK falls back to the standard OID4VP flow. These are the adjustments needed to make sure your verifier web application meets these conditions: ### Add DC API support to your verifier application configuration [#add-dc-api-support-to-your-verifier-application-configuration] 1. Expand the **Credential Verification** section in the left-hand navigation panel. 2. Select **Applications**. 3. Click on the verifier application you want to update. 4. Use the *Supported protocols* checkbox to select **Digital Credentials API (DC API)**. 5. In the *DC API configuration* section that appears, use the checkboxes to specify which platforms should use DC API (at least one must be selected): * Select **Mobile** to enable DC API on mobile browsers. * Select **Desktop** to enable DC API on desktop browsers. 6. Select **Update** to save your changes. Make the following request to your MATTR VII tenant to add the `dcApiConfiguration` block to your [verifier application configuration](/docs/verification/remote-verification-api-reference/verifier-applications#update-a-verifier-application): ```http title="Request" PUT /v2/presentations/applications/{applicationId} ``` * `applicationId`: The ID of the verifier application you want to update. ```json title="Request body" { // ... your existing configuration "dcApiConfiguration": { "supportedBrowserPlatforms": { "mobile": true, "desktop": true } } } ``` * `supportedBrowserPlatforms`: Specify which platforms should use DC API: * `mobile`: Set to `true` to enable DC API on mobile browsers. * `desktop`: Set to `true` to enable DC API on desktop browsers. ### Conditionally omit the walletProviderId when calling the `requestCredentials` method [#conditionally-omit-the-walletproviderid-when-calling-the-requestcredentials-method] When requesting credentials via the DC API, the `walletProviderId` must be omitted (set to `undefined`) to allow the SDK to use the DC API flow. You can achieve this by using the SDK's `isDigitalCredentialsApiSupported` method to check if the user's browser supports DC API and adjusting the request options accordingly (This method returns `true` if the browser supports DC API, `false` otherwise): ```typescript title="Request credentials with DC API" const options: MATTRVerifierSDK.RequestCredentialsOptions = { // The array must contain exactly one query to align with the DC API's single credential requirement. credentialQuery: [credentialQuery], challenge: MATTRVerifierSDK.utils.generateChallenge(), openid4vpConfiguration: { redirectUri: window.location.origin, walletProviderId: MATTRVerifierSDK.isDigitalCredentialsApiSupported() ? undefined : "your-wallet-provider-id", }, }; const results = await MATTRVerifierSDK.requestCredentials(options); ``` ## Accepting credentials from an Apple Wallet via DC API [#accepting-credentials-from-an-apple-wallet-via-dc-api] When accepting credentials from Apple Wallets via the DC API, trust is anchored in an external root CA certificate issued by Apple Business Connect. You must manually create a verification request signer in MATTR VII and link it to the Apple-issued certificate. See [external certificates](/docs/concepts/chain-of-trust#external-certificates) for background. This involves the following steps: 1. [Setup an Apple Business Connect account](#setup-an-apple-business-connect-account). 2. [Create a verification request signer on your MATTR VII tenant](#create-a-mattr-vii-verification-request-signer). 3. [Create a matching Apple Business Connect certificate](#create-an-apple-business-connect-certificate). 4. [Activate the verification request signer](#activate-the-verification-request-signer). ### Setup an Apple Business Connect account [#setup-an-apple-business-connect-account] Set up an Apple Business Account for your company, and register with Apple. See [Apple Business Connect User Guide](https://support.apple.com/guide/apple-business-connect/welcome/web) for more details. ### Create a Certificate Signing Request (CSR) [#create-a-certificate-signing-request-csr] Next you will use MATTR VII to create a Certificate Signing Request (CSR). This will be shared with Apple Business Connect to create a matching certificate. Currently this action can only be performed via an API request. Make the following request to your MATTR VII tenant to [create a verification request signer](/docs/api-reference/platform/verification-request-signers/createVerificationRequestSigner): ```http title="Request" POST /v2/presentations/certificates/verifier-signers ``` ```json title="Request body" { "emailAddress": "user@example.com", "country": "US", "stateOrProvinceName": "AL", "commonName": "my-verifier.example.com", "organizationName": "MATTR Learn", "caType": "apple" } ``` * `emailAddress`: The email address of the domain (or IT) administrator. * `country`: The two-letter country code (ISO 3166-1 alpha-2) representing your company's location. * `stateOrProvinceName`: The company's officially recognized state, province, region, or locale. * `commonName`: Fully qualified domain name (FQDN) where the verifier application is hosted. * `organizationName`: The official name of your company. * `caType`: Set to `apple` to indicate that this signer is for Apple Wallet verification requests. *Response* A successful `201` response indicates that the verification request signer was created successfully: ```json title="Response body" { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", // [!code focus] "csrPem": "-----BEGIN CERTIFICATE REQUEST-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE REQUEST-----", // [!code focus] "active": false, "caType": "apple" } ``` Make note of the following values: * `id`: The verification request signer ID. You will use it later to activate the signer after creating the matching Apple Business Connect certificate. * `csrPem`: You will need it in the next step to create a matching Apple Business Connect certificate in your Apple Developer account. ### Create an Apple Business Connect certificate [#create-an-apple-business-connect-certificate] Log into the [Apple Business Connect](https://businessconnect.apple.com/) portal and create a new Apple Business Connect certificate using the CSR you obtained in the previous step. For detailed instructions, see [Apple's documentation](https://support.apple.com/en-gb/guide/apple-business-connect/abcbffe722af/1.0/web/1.0). Once you create the certificate in Apple Business Connect, download the certificate file in `pem` format. You will use it in the next step to activate the verification request signer in your MATTR VII verifier tenant. ### Activate the verification request signer [#activate-the-verification-request-signer] To activate the verification request signer you created earlier, make the following request to your MATTR VII tenant to [update the verification request signer](/docs/api-reference/platform/verification-request-signers/updateVerificationRequestSigner) and activate it: ```http title="Request" PUT /v2/presentations/certificates/verifier-signers/{verifierSignerId} ``` * `verifierSignerId`: The ID of the verification request signer you created earlier. ```json title="Request body" { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----" } ``` * `active`: Set to `true` to activate the signer. * `certificatePem`: The Apple Business Connect certificate you created in the previous step, in `pem` format. Once the verification request signer is activated, requests coming from your MATTR VII verifier tenant can be validated, as they are signed using the private key associated with this certificate. ## Test the DC API workflow [#test-the-dc-api-workflow] To test DC API integration, you need: 1. A supported browser (see compatibility above). 2. A DC API-compatible wallet with matching credentials: * **Google Developer Wallet**: Available on supported Android devices. * **Apple Wallet**: Available on iOS 26 on an iPhone 11 or later, with either a valid credential or a [Wallet Identity Developer profile](https://developer.apple.com/bug-reporting/profiles-and-logs/?name=wallet\&platform=ios) installed. * **MATTR Labs Wallet**: [Contact us](mailto:dev-support@mattr.global) to get access. The user experience should be seamless—the wallet interface appears directly in the browser without leaving your application. ## Important considerations [#important-considerations] * **Apple Wallet limitations**: To accept credentials from Apple Wallets in production environments, your verifier application must be registered and approved by Apple. For more information, see [Apple's documentation](https://support.apple.com/en-gb/guide/apple-business-connect/abcbffe722af/1.0/web/1.0). * **No pre-flight check**: There's no way to check in advance if the user has a matching credential in a DC API-compatible wallet. If they don't, the wallet will show a "No matching credentials found" message. * **Error handling**: Always implement robust error handling for scenarios where the user lacks compatible credentials or denies consent. # Remote web verification DC API journey pattern URL: /docs/verification/remote-web-verifiers/dc-api/journey-pattern This journey pattern is used to verify an mDoc remotely via an [online verification workflow](/docs/verification/remote-overview), as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) using the [DC API](/docs/verification/remote-web-verifiers/dc-api/overview). ## Overview [#overview] * **Issuance channel**: Remote, unsupervised * **Device/s**: Same-device / Cross-device * **Formats**: mDocs * **Information assurance level**: High * **Identity assurance level**: High ## Journey flow [#journey-flow] mDocs Web Verification Same-device ## Architecture [#architecture] Remote web verification DC API architecture ### Interacting with the verifier application [#interacting-with-the-verifier-application] The user accesses a verifier web application using a supported web browser, on either a desktop or a mobile device. ### Requesting a credential for verification [#requesting-a-credential-for-verification] Within the verifier application, the user initiates an interaction that requires presenting a mobile document (mDoc) for verification. The verifier application embeds the MATTR Verifier Web SDK and first checks whether the user’s browser supports the Digital Credentials API (DC API). If supported, the verifier application uses the SDK to initiate a presentation session with a configured MATTR VII verifier tenant. The request sent to the MATTR VII verifier tenant specifies: * Which credentials are required * Which claims from those credentials are needed for verification The MATTR VII verifier tenant creates a new presentation session and returns a verification request object to the verifier application. ### Invoking the Digital Credentials API [#invoking-the-digital-credentials-api] The verifier application passes the verification request object to the browser to invoke the Digital Credentials API. Based on the user’s device and environment, the browser presents an appropriate verification interface to the user: * On desktop devices, this may be rendered as a QR code * On mobile devices, this may be rendered as an in-browser control (for example, a button) to start the verification directly The user initiates the verification process by scanning the QR code or interacting with the in-browser control. ### Selecting and reviewing a credential [#selecting-and-reviewing-a-credential] Once initiated, the browser forwards the verification request to the user’s mobile device. The mobile operating system displays a system-managed interface listing matching credentials available from installed wallet applications that are registered to handle DC API requests. This interface is rendered by the mobile device and appears on top of the web browser. The user selects a credential to present. * On some platforms (for example, iOS), if only one compatible wallet holds a matching credential, the operating system may directly open that wallet without showing a selection interface. ### Invoking the wallet application [#invoking-the-wallet-application] The wallet application authenticates the user and retrieves the verification request details, including: * Which credential is being requested * Which claims are required * The relying party requesting the information The wallet application displays the credential details to the user for review. This interface is rendered by the wallet application and is displayed on top of the web browser. The user reviews the request and, if comfortable, provides consent to share the credential. ### Verifying the credential [#verifying-the-credential] After consent is given, the wallet application returns an encrypted presentation response to the browser. The browser forwards this presentation response to the verifier application, which then submits it to the MATTR VII verifier tenant. The MATTR VII verifier tenant decrypts and verifies the presentation, performing checks to ensure: * The credential has not been tampered with * The credential has not been revoked or suspended * The credential has not expired * The credential was issued by a trusted issuer ### Displaying verification results [#displaying-verification-results] The MATTR VII verifier tenant returns the verification results to the verifier application. The verifier application displays the results to the user, allowing them to continue their interaction based on the outcome of the verification. The MATTR VII verifier tenant can also be configured to return the verification result to a secure back-end service instead of the front-end, depending on implementation needs. # Digital Credentials API URL: /docs/verification/remote-web-verifiers/dc-api/overview Description: Learn what the W3C Digital Credentials API (DC API) is, how it streamlines digital credential verification on the web, and how MATTR VII verifier capabilities support it alongside OpenID4VP and ISO/IEC 18013-7 to deliver smoother, more privacy-preserving online verification. DC API support is currently offered as a tech preview. The Digital Credentials API specification itself is still under active development in the W3C Web Incubator CG, and platform implementations continue to evolve. As such, functionality may be limited, may not work in all scenarios, and could change or break without prior notice as browsers and operating systems update their implementations. ## What is the Digital Credentials API? [#what-is-the-digital-credentials-api] The **Digital Credentials API (DC API)** is a web browser standard that lets websites request digital credentials, and digital wallets respond, directly through the browser. Instead of redirecting a user from a website to a separate wallet app and back, the browser itself orchestrates the exchange. The user sees a single, consistent prompt and picks the credential that satisfies the request, regardless of which wallet on the device stores it. The DC API is being incubated in the [W3C Web Incubator Community Group](https://wicg.github.io/digital-credentials/) and is incorporated into the [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) technical specification for remote verification of mDocs. This page is written from the **verifier** perspective. For the holder perspective, see the [holder DC API overview](/docs/holding/dc-api/overview). ## Why the Digital Credentials API matters for verifiers [#why-the-digital-credentials-api-matters-for-verifiers] Until recently, online credential verification largely relied on redirect-based flows. A website would invoke a wallet via a custom URL scheme, hand off the user, and wait for the wallet to redirect back with a response. That model works, but it creates real friction: * Users are presented with a long list of possible wallet apps and have to guess which one holds the right credential. * Many users land in a dead-end, having picked a wallet that is not installed or does not hold the credential the verifier is asking for. * Verifiers end up restricting the wallets they support to keep the experience manageable, which limits user choice and slows ecosystem growth. The DC API addresses these issues at the browser and operating system level. Wallets register the credentials they hold with the operating system. When a website asks for a credential, the OS finds matching credentials across every registered wallet and shows the user a single, unified picker. The focus moves from **choosing a wallet** to **choosing the right credential**. For verifiers, this translates into higher completion rates, fewer abandoned journeys, and broader reach. A single integration can serve users regardless of which wallet they happen to use, including wallets built into the operating system itself. ## Where the DC API fits alongside OpenID4VP and ISO/IEC 18013-7 [#where-the-dc-api-fits-alongside-openid4vp-and-isoiec-18013-7] The DC API is a transport, not a replacement for the existing credential exchange protocols. It defines how a verification request and response move between a website and a wallet through the browser. The actual request-and-response messages still follow established standards. | Standard / specification | What it defines | Relationship to the DC API | | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | [OpenID4VP](/docs/verification/oid4vp) | The request-and-response protocol used by a verifier to ask a wallet for a credential | OpenID4VP messages can be carried over redirects or over the DC API. ISO/IEC 18013-7 Annex D defines OpenID4VP over the DC API. | | [ISO/IEC 18013-5](/docs/concepts/mdocs/standards-and-technologies) | mDoc data model and in-person, proximity-based presentation | The DC API is not used for in-person flows. ISO/IEC 18013-5 underpins the credential format and signatures that 18013-7 then carries online. | | [ISO/IEC 18013-7](/docs/concepts/mdocs/standards-and-technologies) | Remote (online) verification of mDocs | Annex C defines mDoc device retrieval over the DC API. Annex D defines OpenID4VP over the DC API. | In practice, this means a single DC API integration can serve both annexes of ISO/IEC 18013-7, and a verifier does not have to choose between OpenID4VP and the DC API. They work together. MATTR's implementation supports both annexes via a single integration in the Verifier Web SDK, providing a consistent verification experience across platforms and browser types. ## How verification works with the DC API [#how-verification-works-with-the-dc-api] A DC API-based verification follows four broad steps. This view focuses on what the **verifier** does at each stage. ### Issue a verification request [#issue-a-verification-request] When a user interacts with a verifier web application that requires a credential, the verifier application initiates a DC API request through the user's browser. The browser invokes the DC API, which communicates with the mobile device and shares a request object. The request object includes a presentation definition detailing the information the verifier requires, such as the type and format of credentials and any individual claims from those credentials. ### The operating system gathers matching credentials [#the-operating-system-gathers-matching-credentials] The mobile device queries all credentials that wallets have registered with the operating system as available for DC API requests. Matching credentials are presented to the user in a unified picker, regardless of which wallet application stores them. From the verifier's point of view this stage is opaque. The verifier does not need to know which wallet ultimately serves the request, the integration is the same in either case. ### The wallet returns a verifiable presentation [#the-wallet-returns-a-verifiable-presentation] Once the user selects a credential and consents to share it, the wallet sends an encrypted verifiable presentation back through the DC API to the verifier application. The browser ensures the response returns to the exact context where the request originated, maintaining session continuity and security. ### The verifier validates the presentation [#the-verifier-validates-the-presentation] Once the verifier receives the verifiable presentation, the application decrypts it and applies verification checks to validate the credential's authenticity, the issuer's trust status, and the response's compliance with the original presentation request. The DC API supports both [same-device](#same-device-verification) and [cross-device](#cross-device-verification) flows. A single DC API integration handles both. ## Same-device verification [#same-device-verification] Same-device flows involve a single mobile device running both the verifier web application and the wallets that store the user's credentials. For example, consider a user applying for a loan through their bank's mobile website. When the site needs to verify the user's identity, it triggers a DC API request in the browser. The browser uses the DC API to query all credentials registered on the device and display the ones that can satisfy the request. The user authenticates, selects their driver's license, and approves sharing it. The wallet then creates and signs the presentation and returns it through the browser to the verifier application. The DC API manages the entire flow within the browser context, eliminating the need for redirects or leaving the user's current experience. The user authenticates using platform-level biometrics, and the private key associated with the credential remains secure in the device's key store. ## Cross-device verification [#cross-device-verification] Cross-device flows involve two devices: * One device (typically a desktop or laptop) runs the verifier application in a web browser and creates the presentation request. * A second device (typically a mobile phone) houses wallets that store the required credentials and responds to the request. The browser on the first device displays a QR code or other mechanism to initiate the cross-device flow. The user scans this with their mobile device, which invokes the DC API on the mobile platform. The mobile device queries registered credentials and presents them to the user. For example, a user completing a tax return on their desktop computer is asked to verify their identity. The website displays a QR code, which the user scans with their phone. The mobile device then presents matching registered credentials, the user selects their national ID card, authenticates, and consents to sharing the information. The presentation is sent back to the desktop browser, where verification completes and the tax return process continues. The DC API uses secure communication protocols with built-in proximity checks (such as CTAP 2.1 over Bluetooth Low Energy) to verify the devices are physically close together. This mitigates risks such as QR code replay attacks or session hijacking. ## Security and privacy properties [#security-and-privacy-properties] The DC API provides several security and privacy advantages over redirect-based approaches that are particularly relevant for verifiers: * **Session continuity**: Once the interaction completes or if the user cancels, the session continues in the original browser context without losing state or requiring navigation. The verifier does not have to design around redirect bounces. * **Proximity verification in cross-device flows**: Cross-device flows use operating system-level proximity checks to ensure devices are physically near each other before proceeding, reducing risks from QR code screenshots or remote attacks. * **Reduced tracking surface**: The DC API prevents unauthorized apps from silently tracking user interactions or accessing credential data without explicit user action, which lowers the verifier's exposure to abuse vectors that target redirect-based flows. ## Platform support [#platform-support] The DC API is a web browser standard and is implemented differently across platforms: * **iOS**: Supported on Safari version 26 and later, available on iPhone 11 and later running iOS 26. * **Android**: Supported on Chrome and Chromium-based browsers version 138 and later. The DC API is specifically designed for web applications running in browsers. Native mobile applications use platform-specific APIs to interact with digital credentials. For example, Android provides a platform-specific implementation called the DigitalCredential API, which is part of the Credential Manager framework. ## How MATTR supports the DC API on the verifier side [#how-mattr-supports-the-dc-api-on-the-verifier-side] The [MATTR VII Verifier Web SDK](/docs/verification/remote-web-verifiers/dc-api/guide) embeds the DC API as a streamlined alternative to redirect-based OpenID4VP. The same SDK: * Handles both **same-device** and **cross-device** flows with a single integration. * Supports both **Annex C** (mDoc device retrieval over DC API) and **Annex D** (OpenID4VP over DC API) of ISO/IEC 18013-7. * Accepts presentations from **MATTR-built wallets**, **third-party wallets**, and **OEM wallets** through one consistent integration. This means a verifier integration built on MATTR VII does not need to be redesigned each time a new wallet category becomes important. The DC API surface stays the same. ## Accepting credentials from OEM wallets [#accepting-credentials-from-oem-wallets] One of the most significant capabilities the DC API unlocks is the ability to **accept credentials from OEM wallets**. OEM wallets are credential wallets shipped natively on mobile devices by their manufacturers, such as those provided by Apple and Google. They are pre-installed, deeply integrated with the operating system, and increasingly used by governments and large issuers to distribute high-assurance identity credentials such as mobile driver's licenses and national identification cards. Historically, integrating with an OEM wallet meant a separate, bespoke integration per wallet provider. The DC API changes this. Because OEM wallets register their credentials with the operating system just like any other DC API-compatible wallet, a verifier that implements the DC API can accept presentations from OEM wallets through the same integration it uses for any other wallet. For organizations exploring this, the relevant questions are usually: * Which OEM wallets are available in the jurisdictions and on the device platforms my users rely on? * Which credential formats do those OEM wallets support, and how do those map to the credentials my verifier needs to accept? * How do trust frameworks, issuer onboarding, and certification interact with OEM wallet acceptance? MATTR works with relying parties, governments, and ecosystem partners on exactly these questions. If you are looking to accept credentials from OEM wallets, or to understand which OEM wallets your verifier should support, [contact us](mailto:dev-support@mattr.global) so we can talk through the specifics of your deployment. ## Frequently asked questions [#frequently-asked-questions] ### What is the Digital Credentials API (DC API)? [#what-is-the-digital-credentials-api-dc-api] The **Digital Credentials API (DC API)** is a web browser standard being incubated in the W3C Web Incubator Community Group. It defines how websites and browsers can request and present digital credentials directly through the browser, without redirecting the user to a separate wallet app. The DC API is also incorporated into **ISO/IEC 18013-7** for remote verification of mDocs. ### Why does the Digital Credentials API matter for web verification? [#why-does-the-digital-credentials-api-matter-for-web-verification] The DC API removes long-standing friction in online credential verification. Instead of asking users to pick between multiple wallet apps and bounce through redirects, the browser shows a single prompt with credentials that actually match the request. This improves completion rates, reduces dead-ends, and lets verifiers accept credentials from a wider set of wallets, including those built into mobile operating systems. ### How does the DC API relate to OpenID4VP? [#how-does-the-dc-api-relate-to-openid4vp] **OpenID4VP** is the request-and-response protocol that defines how a verifier asks for a credential and how a wallet replies. The **DC API** is the browser transport that carries those messages between the website and the wallet. OpenID4VP can be carried over redirects or over the DC API, and ISO/IEC 18013-7 Annex D describes how OpenID4VP runs over the DC API. ### How does the DC API relate to ISO/IEC 18013-7? [#how-does-the-dc-api-relate-to-isoiec-18013-7] **ISO/IEC 18013-7** defines remote verification of mDocs. **Annex C** describes mDoc device retrieval over the DC API (currently supported on iOS and Safari), and **Annex D** describes OpenID4VP over the DC API (expected on Android and Chromium browsers). The DC API is the browser-level mechanism both annexes rely on. ### Which browsers and platforms currently support the DC API? [#which-browsers-and-platforms-currently-support-the-dc-api] The DC API is supported on **iOS 26 and later with Safari 26** (on iPhone 11 and later), and on **Chrome and Chromium-based browsers version 138 and later** on Android. Implementations are evolving, so the exact behavior can change as browsers and operating systems update. ### Can verifiers accept credentials from OEM wallets via the DC API? [#can-verifiers-accept-credentials-from-oem-wallets-via-the-dc-api] Yes. One of the most important capabilities the DC API unlocks is **accepting credentials from OEM wallets**, which are wallets shipped natively by device manufacturers such as Apple and Google. Because the DC API lets the operating system aggregate credentials from any registered wallet, verifiers no longer need to integrate with each wallet individually. MATTR's verification capabilities are designed to accept presentations from OEM wallets via the DC API. To explore this for your deployment, [contact us](mailto:dev-support@mattr.global). ### How does MATTR support the DC API on the verifier side? [#how-does-mattr-support-the-dc-api-on-the-verifier-side] The **MATTR VII Verifier Web SDK** integrates the DC API as a streamlined alternative to redirect-based OpenID4VP. A single integration handles both same-device and cross-device flows, supports both Annex C and Annex D of ISO/IEC 18013-7, and lets verifiers accept presentations from MATTR-built wallets, third-party wallets, and OEM wallets through one consistent integration. # Remote web verification DC API Workflow URL: /docs/verification/remote-web-verifiers/dc-api/workflow DC API support is currently offered as a tech preview. The Digital Credentials API specification itself is still under active development in the W3C Web Incubator CG, and platform implementations continue to evolve. As such, functionality may be limited, may not work in all scenarios, and could change or break without prior notice as browsers and operating systems update their implementations. mDocs are digital credentials based on the ISO/IEC [18013-5](https://www.iso.org/standard/69084.html) standard and [18013-7](https://www.iso.org/standard/91154.html) technical specification, designed to be stored on a holder’s mobile device and support either in-person or remote verification workflows. The purpose of this page is to describe the end-to-end [remote web app verification](/docs/verification/remote-overview) DC API workflow, where a user interacts with a web application to present an mDoc stored on their mobile device via the DC API, as per Annex C and D of ISO/IEC 18013-7:2025. ## Prerequisites [#prerequisites] We recommend you make yourself familiar with the following concepts to support your understanding of the implementation described in this page: * What is [credential verification](/docs/verification)? * What are [mDocs](/docs/concepts/mdocs)? * What is [remote verification](/docs/verification/remote-overview)? * What is the [DC API](/docs/verification/remote-web-verifiers/dc-api/overview)? ## Overview [#overview] The DC API is a browser standard that enables web applications to request and verify digital credentials directly from compatible wallet applications on the user's device. This provides a seamless user experience within a platform-managed flow initiated from the browser. The API allows wallets to register as credential providers with the mobile device's operating system, making them automatically discoverable when a website requests credentials. ## Detailed workflow [#detailed-workflow] Let's take a closer look at the end-to-end workflow and explain the role of each component: ### Invoking the interaction [#invoking-the-interaction] The user triggers an action in a verifier web application that requires presenting a credential for verification. ### Generating challenge [#generating-challenge] When the verifier web application has a backend, the backend generates a unique challenge to ensure the security of the verification workflow. This challenge will be used to validate that the verification results received later are associated with this specific session. Generating the challenge on the backend helps prevent tampering and replay attacks, as the challenge is not exposed to the frontend until verification is complete. Furthermore, it enables the backend to associate the verification results with an existing session or transaction, enhancing the overall security and integrity of the verification process. ### Passing challenge to web application [#passing-challenge-to-web-application] The verifier backend sends the generated challenge to the verifier web application, which will include it when starting the presentation session with MATTR VII. ### Checking browser support [#checking-browser-support] The verifier web application checks if the user's browser supports the DC API. ### Requesting credentials [#requesting-credentials] The **verifier web application** calls the SDK's `requestCredentials` method to start a presentation session with the configured **MATTR VII tenant**, including the challenge received from the backend. ### Creating presentation session [#creating-presentation-session] The **MATTR VII tenant** creates a new presentation session and returns the request object to the **verifier web application**. ### Invoking DC API [#invoking-dc-api] The **verifier web application** passes the request object to the user's **browser** to invoke the Digital Credentials API. ### Presenting verification request [#presenting-verification-request] The **browser** presents a verification request interface to the **user**. This can be a QR code (when the user is on a desktop) or a button to start the process directly in the browser (when the user is on a mobile device). ### Initiating verification [#initiating-verification] The **user** initiates the verification process by scanning the QR code/clicking the button. ### Forwarding request to mobile device [#forwarding-request-to-mobile-device] The **browser** forwards the request to the **mobile device**. ### Displaying matching credentials [#displaying-matching-credentials] The **mobile device** displays to the **user** matching credentials from installed **wallet applications** that are registered to handle DC API requests. This UI is rendered by the **mobile device** and is displayed on top of the web browser. ### Selecting credential [#selecting-credential] The **user** selects a credential from the displayed options. ### Forwarding to wallet [#forwarding-to-wallet] The **mobile device** forwards the verification request to the **wallet application** that holds the selected credential. On iOS devices, if there is only one compatible wallet that holds a matching credential, the system may directly open that wallet without displaying the selection UI to the user. ### Reviewing credential details [#reviewing-credential-details] The mobile device displays the credential details to the **user** for review and consent. This UI is rendered by the **wallet application** and is displayed on top of the web browser in iOS, while on Android it is displayed within the system's native UI. ### Providing consent [#providing-consent] The **user** reviews the credential details and provides consent to share them with the verifier. ### Returning presentation response [#returning-presentation-response] The **wallet application** returns the encrypted credential as a presentation response to the **browser**. ### Forwarding to Verifier [#forwarding-to-verifier] The **browser** forwards the presentation response to the **verifier web application**. ### Submitting for verification [#submitting-for-verification] The **verifier web application** submits the presentation response to the **MATTR VII tenant** for verification. ### Verifying credential [#verifying-credential] The **MATTR VII tenant** decrypts and verifies the credential. ### Notifying verification completion [#notifying-verification-completion] The **MATTR VII tenant** notifies the **verifier web application** that verification has been completed. When the web application has a backend, the tenant does not send the verification results directly to the frontend for enhanced security. ### Passing session ID to backend [#passing-session-id-to-backend] The **verifier web application** sends the presentation session ID to the **verifier backend** to retrieve the verification results securely. ### Retrieving verification results [#retrieving-verification-results] The **verifier backend** makes a [request](/docs/verification/remote-verification-api-reference/presentation-sessions#retrieve-a-presentation-session-result) to **MATTR VII** to retrieve the verification results for the session using the session ID. ### Returning results with challenge [#returning-results-with-challenge] The **MATTR VII tenant** responds with the verification results and the unique challenge that was included when the presentation session was created. ### Validating challenge [#validating-challenge] The **verifier backend** compares the original challenge it generated with the challenge received from MATTR VII to ensure the response can be trusted and is associated with the correct session. ### Passing results to web application [#passing-results-to-web-application] Once the challenge is validated, the **verifier backend** sends the verification results to the **verifier web application**. ### Displaying results [#displaying-results] The **verifier web application** displays the verification results and the **user** continues the interaction accordingly. Based on the unique challenge, the verifier can also continue any backend processes associated with the verification session, such as granting access to a service or completing a transaction. # Correlating verification sessions with your system URL: /docs/verification/remote-web-verifiers/guides/correlating-verification-sessions Description: Learn how to use the optional state parameter to link a MATTR VII verification session to a record in your own application without implementing separate state management. When a web application starts a remote verification session, MATTR VII generates its own `sessionId` (a UUID) to track the session through its lifecycle. That identifier is fine for referring to the session within MATTR VII, but it does not map to anything in your own system. If you need to attach the verification to an existing record on your side, such as an onboarding application, a checkout intent, or an account opening flow, you need a correlation reference of your own. The `state` parameter on `requestCredentials()` is intended for exactly this. You pass an opaque string that means something in your system, and MATTR VII carries it through the verification session and returns it to you with the result. No additional state management is required on your application or backend. ## When to use `state` [#when-to-use-state] Use `state` when your application has an existing record that a verification session needs to be attached to, and you want a single value that: * You generate and control. * Travels through the entire session, including the wallet interaction. * Comes back to you in both same-device and cross-device flows. * Is available on both successful and failed results. Typical examples include an onboarding application reference, a checkout or transaction identifier, or any internal record ID that needs to be reconciled with the verification outcome. If you only need to verify a credential and immediately act on the result in the same browser session, `state` is not required. The MATTR VII `sessionId` is enough on its own. Use `state` when correlation needs to survive the round-trip to the wallet and back. The `state` value is transmitted in plain text as a query parameter on the wallet-facing authorization request URI and is stored in session records. It should not contain personally identifiable information (PII), credentials, or anything sensitive. Use an opaque reference, such as an internal record ID or a randomly generated token your system can map back to the underlying record. ## How `state` flows through a session [#how-state-flows-through-a-session] When you supply `state`, MATTR VII threads the value through the OpenID4VP presentation flow: 1. The value is persisted with the session when it is created. 2. It is appended as a plain `&state={value}` query parameter on the wallet-facing authorization request URI, making it readable to the wallet without parsing the signed request object. 3. It is used as the OpenID4VP `state` claim inside the signed request object. When `state` is not supplied, MATTR VII falls back to its internal `sessionId` for this claim. 4. The wallet's response is validated against the stored value. A mismatch results in a failure result with a `VerificationError`, the same behavior as when no `state` is supplied and a wallet echoes an unexpected value. 5. The value is returned to your application in the session result, on both success and failure shapes. This flow applies to OpenID4VP browser flows in both same-device and cross-device variants. It does not apply to the Digital Credentials API flow. ## Setting `state` when starting a session [#setting-state-when-starting-a-session] Pass `state` alongside the other options when calling `requestCredentials()` in the Verifier Web SDK: ```typescript title="Verifier Web SDK example" const options: MATTRVerifierSDK.RequestCredentialsOptions = { credentialQuery: [credentialQuery], challenge: MATTRVerifierSDK.utils.generateChallenge(), state: "onboarding-app-7f3a4e8c", // [!code focus] openid4vpConfiguration: { redirectUri: window.location.origin, walletProviderId: process.env.NEXT_PUBLIC_WALLET_PROVIDER_ID, }, }; const results = await MATTRVerifierSDK.requestCredentials(options); ``` The value you choose is opaque to MATTR VII. Generate it in whatever way fits your system, for example by reusing an existing internal record ID, by minting a fresh random token at the moment you start the verification, or by deriving a one-way reference from a database row. ### Constraints [#constraints] `state` is optional. When provided it should be a non-empty string of at most 256 characters. A request that supplies an empty string or a value longer than 256 characters is rejected with an HTTP 400 validation error. The 256-character limit is enforced at the API boundary and is sufficient for any UUID, opaque token, or short reference ID. If your internal identifier is longer, store it on your side and pass a shorter reference that maps back to it. ## Reading `state` back [#reading-state-back] `state` is returned everywhere a session result is exposed: in the SDK return values for both same-device and cross-device flows, and in the back-channel result endpoint used by your backend. It appears on both `PresentationSuccessResult` and `PresentationFailureResult`, so you can correlate failures as well as successes. ### Cross-device flow (front channel) [#cross-device-flow-front-channel] In cross-device flows, the Verifier Web SDK resolves results in the same call that started the session. The `state` you supplied is echoed in the returned `RequestCredentialsResponse`: ```typescript type RequestCredentialsResponse = { sessionId: string; state?: string; result?: PresentationSessionResult; sessionCompletedInRedirect?: boolean; }; ``` ```typescript title="Reading state from the cross-device result" const results = await MATTRVerifierSDK.requestCredentials(options); if (results.isOk()) { const { state, result } = results.value; // state === "onboarding-app-7f3a4e8c" // Use state to look up the matching record in your system. } ``` ### Same-device flow (front channel) [#same-device-flow-front-channel] In same-device flows, the wallet redirects the user back to your `redirectUri` after the presentation completes, and your application calls `handleRedirectCallback()` to retrieve the result. The returned `state` is the same value you supplied at session creation: ```typescript type HandleRedirectCallbackResponse = { sessionId: string; state?: string; result?: PresentationSessionResult; }; ``` ```typescript title="Reading state from the same-device redirect callback" const results = await MATTRVerifierSDK.handleRedirectCallback(); if (results.isOk()) { const { state, result } = results.value; // state === "onboarding-app-7f3a4e8c" } ``` The SDK persists `state` to `localStorage` before redirecting the browser to the wallet and cleans it up after `handleRedirectCallback()` completes. This local persistence is an implementation detail of the same-device flow and is needed because some relying-party frameworks strip query parameters from redirect URIs. You do not need to read or write this storage entry yourself. ### Back channel [#back-channel] When your verifier application is configured with `resultAvailableInFrontChannel: false`, your backend retrieves results by calling the [retrieve presentation session result](/docs/api-reference/platform/mdocs-presentation-sessions/getPresentationResult) endpoint. The response body includes the same `state` field, present only when a value was supplied at session creation: ```json title="Example back-channel result with state" { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "state": "onboarding-app-7f3a4e8c", "credentialQuery": [ { "profile": "mobile", "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": { "intentToRetain": false }, "given_name": { "intentToRetain": false } } } } ], "credentials": [ { "docType": "org.iso.18013.5.1.mDL", "claims": { "org.iso.18013.5.1": { "family_name": { "value": "Smith" }, "given_name": { "value": "Jane" } } }, "verificationResult": { "verified": true } } ] } ``` The same field appears on failure results, allowing you to correlate failed sessions to your internal record: ```json title="Example back-channel failure result with state" { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "state": "onboarding-app-7f3a4e8c", "credentialQuery": [ { "profile": "mobile", "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": { "intentToRetain": false } } } } ], "error": { "type": "SessionAborted", "message": "User aborted the session" } } ``` `state` is a correlation reference, not a security mechanism. It does not replace the challenge used to detect session replay. When using back-channel delivery, your backend should still generate a unique `challenge` per session and validate the value returned in the result against the one you stored. See the [handling verification results guide](/docs/verification/remote-web-verifiers/guides/handling-verification-results) for the full back-channel pattern. ## A worked example [#a-worked-example] A common pattern is to mint an internal reference when a user starts a verification step, record it against the user's session in your system, pass it as `state`, and use it on the return trip to find the right record. ```typescript title="Web application" // Mint or retrieve an internal reference for this user's onboarding step. const applicationRef = await fetch("/api/onboarding/start", { method: "POST" }) .then((r) => r.json()) .then((r) => r.applicationRef); const options: MATTRVerifierSDK.RequestCredentialsOptions = { credentialQuery: [credentialQuery], challenge: await createChallenge(), state: applicationRef, openid4vpConfiguration: { redirectUri: `${window.location.origin}/onboarding/complete`, walletProviderId: process.env.NEXT_PUBLIC_WALLET_PROVIDER_ID, }, }; await MATTRVerifierSDK.requestCredentials(options); ``` ```typescript title="Same-device callback handler" const results = await MATTRVerifierSDK.handleRedirectCallback(); if (results.isOk()) { const { sessionId, state, result } = results.value; // Hand sessionId and state to your backend so it can fetch the result // and attach it to the correct application record. await fetch("/api/onboarding/complete", { method: "POST", body: JSON.stringify({ sessionId, applicationRef: state }), }); } ``` ```typescript title="Backend result handler" // In your backend handler for /api/onboarding/complete: const result = await fetchPresentationResult(sessionId); if (result.challenge !== storedChallengeFor(sessionId)) { throw new Error("Challenge mismatch"); } // result.state is the same applicationRef the web application supplied. await applications.attachVerificationResult(result.state, result); ``` This pattern lets your backend reconcile the verification outcome with the originating application record without maintaining a separate `sessionId`-to-application mapping. ## When `state` is not supplied [#when-state-is-not-supplied] `state` is fully optional and additive. If you do not pass it: * No `&state=` query parameter is added to the authorization request URI. * The OpenID4VP `state` claim in the signed request object falls back to the MATTR VII `sessionId`. * The same-device redirect URI is unchanged. * No `state` field appears in any result, on either success or failure shapes. Existing integrations that do not pass `state` will see no change in behavior. ## Next steps [#next-steps] * Review the [Verifier Web SDK API reference](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/) for complete type definitions. * See the [handling verification results guide](/docs/verification/remote-web-verifiers/guides/handling-verification-results) for the broader result structure and the back-channel challenge validation pattern. * See the [workflow guide](/docs/verification/remote-web-verifiers/workflow) for where the `state` value sits within the end-to-end remote verification flow. # Handling verification results URL: /docs/verification/remote-web-verifiers/guides/handling-verification-results Description: Learn how to retrieve and interpret mDoc verification results in your web application When a holder responds to a web verification request, that response includes structured data containing the verification outcome. This guide explains the different types of responses your web application can receive as a verifier and how to access them. ## What gets verified [#what-gets-verified] Before working through the response structure, it is worth being clear about what is actually being verified. The credential itself, the full mDoc as issued and stored in the holder's wallet, never leaves the wallet and is never shared with a verifier. What the holder shares is a *credential presentation*: a subset of the credential's data, accompanied by the issuer's signature over the released items and a device authentication that proves the holder's device authorized this specific presentation. This model is grounded in [ISO/IEC 18013-7](https://www.iso.org/standard/91154.html), which defines remote mDL and mDoc verification and specifies the wire protocols used to exchange a presentation between holder and verifier: the OpenID for Verifiable Presentations (OID4VP) protocol (Annex B), and the W3C Digital Credentials (DC) API integration (Annex C and Annex D). Regardless of the wire protocol, what the holder shares is a presentation derived from the credential rather than the credential itself, and the presentation carries the same cryptographic proof and integrity guarantees as the credential it is drawn from. When you verify a response, you are verifying the credential presentation. The cryptographic trust checks (issuer signature, issuer trust, validity, revocation, device key binding) apply to that presented slice, not to the credential as a whole on the holder's device. The MATTR VII `verificationResult.verified` flag reflects whether this presented credential passed those checks. The rest of this guide uses "credential" as shorthand for the presented credential when the context makes clear we are talking about what was returned in the response. ## Result delivery methods [#result-delivery-methods] The way your web application receives verification results depends on your MATTR VII verifier application configuration: **Front channel delivery** (`resultAvailableInFrontChannel: true`): Results are returned directly to your web application through the Verifier Web SDK as a [`RequestCredentialsResponse`](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/types/RequestCredentialsResponse.html) object. **Back channel delivery** (`resultAvailableInFrontChannel: false`): Your backend retrieves results by calling the [retrieve presentation session result](/docs/api-reference/platform/mdocs-presentation-sessions/getPresentationResult) endpoint, receiving them as a `200` response. For more details on how these delivery methods work within the complete verification flow, see the [workflow guide](/docs/verification/remote-web-verifiers/workflow). Production implementations should use back channel delivery with a backend. This provides better security through challenge validation, protecting against session replay attacks. Regardless of the delivery method, the same information is available in both response structures. The key difference is where and how you retrieve it. ## Understanding response types [#understanding-response-types] When a holder responds to a verification request, you receive one of two high-level result types, depending on whether the presentation workflow was completed successfully or not. ### Presentation failed [#presentation-failed] In this case, the holder was unable to complete the presentation workflow. This can happen for various reasons, such as the user canceling the request, the wallet being unavailable, or an error occurring during presentation processing. You will receive a `PresentationFailureResult` object containing: * **`sessionId`**: The unique identifier for this verification session * **`challenge`**: The challenge used to verify response authenticity * **`credentialQuery`**: The original request defining what credentials and claims were requested * **`error`**: Object with `type` and `message` fields * **`state`** (optional): The caller-supplied correlation reference, present only when a `state` value was provided when starting the session. See [Correlating verification sessions](/docs/verification/remote-web-verifiers/guides/correlating-verification-sessions). The `error.type` indicates why the presentation failed: * **`WalletUnavailable`**: The wallet appears to be unavailable and unable to respond to the request * **`SessionAborted`**: User explicitly aborted the session before it timed out * **`ResponseError`**: Received an error presentation response * **`VerificationError`**: The submitted presentation response is invalid * **`Unknown`**: Encountered an unknown error while processing the presentation response **Example**: User cancels the mDL presentation request: ```json title="Example presentation failure response for user cancellation" { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "credentialQuery": [ { "profile": "mobile", "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": { "intentToRetain": false }, "given_name": { "intentToRetain": false }, "birth_date": { "intentToRetain": false } } } } ], "error": { "type": "SessionAborted", "message": "User aborted the session" } } ``` ### Presentation succeeded [#presentation-succeeded] In this case, the holder successfully completed the presentation workflow. You will receive a `PresentationSuccessResult` object containing: * **`sessionId`**: The unique identifier for this verification session * **`challenge`**: The challenge used to verify response authenticity * **`credentialQuery`**: The original request defining what credentials and claims were requested * **`credentials`** (optional): Array of verified credentials with their claims and verification status * **`credentialErrors`** (optional): Array of errors for credentials that couldn't be returned * **`state`** (optional): The caller-supplied correlation reference, present only when a `state` value was provided when starting the session. See [Correlating verification sessions](/docs/verification/remote-web-verifiers/guides/correlating-verification-sessions). A successful presentation can include several scenarios, often in combination. Each one is described along two independent axes: whether the requested credential itself verified, and whether the requested claims within it were provided. 1. **Requested credential not presented**: The holder doesn't have the requested credential, so there is nothing to verify. The credential appears under `credentialErrors` with an error code of `notReturned`, and no `verificationResult` is produced. 2. **Presented credential not verified**: A credential was provided but the credential-level trust checks failed. The `credentials` array contains the credential with `verified: false` and a `reason.type` explaining why. Even if claims were returned, they should not be relied on while the credential itself did not verify. 3. **Presented credential verified, all claims provided**: The credential-level checks pass (`verified: true` with no `reason`) and every requested claim is returned (no `claimErrors`). Both layers are clean. 4. **Presented credential verified, some claims missing**: The credential-level checks pass (`verified: true`), but one or more requested claims were not returned and appear under `claimErrors`. The credential is trustworthy; the data payload is incomplete. How to handle the missing data is up to the relying party, depending on its use case. Options include failing the flow, falling back to collecting the required data through another channel, or proceeding if the missing claim is not essential. ## Detailed result structure [#detailed-result-structure] When a presentation succeeds, the result includes detailed information at multiple levels: ### Credential-level information [#credential-level-information] Each credential in the `credentials` array contains: * **`docType`**: The credential type (e.g., `org.iso.18013.5.1.mDL` for a mobile driver's license). * **`claims`**: Verified claims organized by namespace. * **`claimErrors`**: Errors for individual claims that couldn't be verified or were not returned. * **`validityInfo`**: Credential validity period timestamps. * **`verificationResult`**: Verification status containing: * **`verified`**: Boolean indicating if verification succeeded (this is a high-level result and individual claim errors may still exist). * **`reason`** (optional): Object with `type` and `message` explaining verification failures (if such failures exist). The following `reason.type` values are possible: * `DeviceKeyInvalid`: Device key is not valid. This can occur if the credential was not properly bound to the device or if the device's secure element is compromised. * `InvalidSignerCertificate`: Invalid signer certificate. This can occur if the credential's signing certificate is not valid or has been tampered with. * `IssuerNotTrusted`: Credential was issued by a certificate that is not trusted by the verifier. This can occur if the issuer's certificate is not included in the verifier's trusted issuer list. * `MobileCredentialExpired`: Credential expired. This can occur if the current date is after the credential's `validUntil` date. * `MobileCredentialInvalid`: Credential is not valid. This can occur for various reasons such as failing signature verification, containing invalid data, or not conforming to expected formats. * `MobileCredentialNotYetValid`: Credential not yet valid. This can occur if the current date is before the credential's `validFrom` date. * `StatusRevoked`: Credential has been revoked. This can occur if the issuer has revoked the credential after it was issued, which is typically checked through a revocation mechanism provided by the issuer. * `StatusUnknown`: Credential status could not be determined. This can occur if the verifier is unable to check the revocation status of the credential due to network issues or if the issuer does not provide a revocation mechanism. * `TrustedIssuerCertificateExpired`: Trusted issuer certificate expired. This can occur if the certificate of the trusted issuer has passed its expiration date, which may affect the trustworthiness of credentials issued by that issuer. * `TrustedIssuerCertificateNotYetValid`: Trusted issuer certificate not yet valid. This can occur if the certificate of the trusted issuer is not yet valid (i.e., the current date is before the certificate's `validFrom` date), which may affect the trustworthiness of credentials issued by that issuer. * `UnsupportedCurve`: Credential object contains unsupported curve. This can occur if the credential uses cryptographic curves that are not supported by the verifier's cryptographic library, which may prevent successful verification of the credential's signatures. * **`issuerInfo`**: Issuer details including `commonName` and `trustedIssuerId`. * **`branding`** (optional): Visual information for displaying the credential (name, description, colors, logos). You can use this information to create a rich user interface when showing the credential details in your application. ### Claim-level information [#claim-level-information] Claims are organized by namespace within the `claims` object. For example, for an mDL, claims will appear under the `org.iso.18013.5.1` namespace: ```json title="Example claims structure for a verified mDL credential" "claims": { "org.iso.18013.5.1": { "family_name": { "value": "Smith" }, "given_name": { "value": "Jane" }, "birth_date": { "value": "1990-05-15" }, "address": { "value": "123 Main Street, Springfield" } } } ``` If specific claims couldn't be verified or weren't provided, they appear in the `claimErrors` object with the same namespace structure and an error code of `notReturned`: ```json title="Example claim errors structure for a credential with a missing claim" "claimErrors": { "org.iso.18013.5.1": { "portrait": "notReturned" } } ``` ### Credential errors [#credential-errors] When a requested credential wasn't provided by the holder, it appears in the `credentialErrors` array with a `docType` and an `errorCode` of `notReturned`: ```json title="Example credential errors structure for a missing credential" "credentialErrors": [ { "docType": "org.iso.18013.5.1.mDL", "errorCode": "notReturned" } ] ``` ## Understanding the `verified` flag [#understanding-the-verified-flag] With the structure in mind, it is worth being explicit about what `verificationResult.verified` does and does not tell you, because it is the single most important field for deciding whether to trust a presentation. `verified` is the pass/fail flag for the presented credential. It applies to the slice of credential data the holder released in this session, together with its associated issuer signature and device authentication, not to the credential as a whole as stored on the holder's device. When `verified: true`, what the holder presented has passed MATTR VII's cryptographic and trust checks: the issuer signature on the released items is intact, the credential was issued by a trusted issuer, it is not expired or revoked, and the device key binding holds. In plain terms, the presented mDoc is genuine, has not been tampered with, and is currently valid. What `verified: true` does not mean is that every claim you requested came back. `verified` is a high-level result on the credential as a whole, and individual claim errors may still exist. A credential can return `verified: true` while a specific requested claim (for example, `portrait`) is missing and surfaces under `claimErrors` with an error code of `notReturned`. The credential is trustworthy; the data payload may still be incomplete. The mirror image is `verified: false`. When the credential layer fails, MATTR VII surfaces a `reason.type` explaining why (for example, expired, revoked, untrusted issuer, invalid signer certificate, or broken device key binding). The `reason` field is typed as optional, so defensive code should handle the case where it is absent. See the [credential-level information](#credential-level-information) section above for the full list of `reason.type` values. ### Two layers of checks [#two-layers-of-checks] A relying party's business logic needs to check two distinct things: 1. **Is the credential real and valid?** Look at `verificationResult.verified` on each credential in the `credentials` array. This is the objective trust check, and it should not be overridden by business logic. 2. **Did we get all the data we asked for?** Look at `claimErrors` on each credential, and at `credentialErrors` on the top-level result. Whether a missing claim or credential is acceptable is a business-logic decision specific to your use case. In other words, `verified` is the objective measure of what can or cannot be accepted at all. Everything else, including `claimErrors`, `credentialErrors`, and the specific claim values returned, feeds your business logic about whether to accept the presentation for your particular use case. Treating `verified: true` as a green light to proceed without inspecting `claimErrors` is a common integration mistake. The credential may be cryptographically valid while still missing a field your use case requires. For example, an age-restricted purchase flow needs `birth_date`, but if only `family_name` came back the relying party cannot make an age decision even though the credential itself returned `verified: true`. Conversely, bypassing a `verified: false` result with business logic (for example, accepting an expired or revoked credential because the claims look correct) defeats the trust model and should not be done. `verified` is MATTR VII's API shape. The underlying trust evaluation for mDL and mDoc credentials follows ISO/IEC 18013-5, but the `verified` boolean itself is not a standards-defined field. ## Complete examples [#complete-examples] ### Requested credential not presented [#requested-credential-not-presented] Neither layer of the check produces a result here. The holder did not provide a matching mDL, so the credential layer has nothing to verify (no `verificationResult` is returned) and the claims layer is not applicable. The missing credential is reported under `credentialErrors`, and the relying party's business logic must decide how to handle it. ```json title="Example response for a requested credential that was not presented" { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "credentialQuery": [ { "profile": "mobile", "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": { "intentToRetain": false }, "given_name": { "intentToRetain": false } } } } ], "credentialErrors": [ { "docType": "org.iso.18013.5.1.mDL", "errorCode": "notReturned" } ] } ``` ### Presented credential not verified [#presented-credential-not-verified] The credential layer fails. The holder provided an mDL but the credential-level trust checks failed (in this example, because the credential has expired): `verified: false`, with `reason.type` identifying why. Even if claims were returned, they should not be relied on while the credential itself did not verify. The credential should not be accepted regardless of business logic. ```json title="Example response for a presented credential that did not verify (expired mDL)" { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "credentialQuery": [ { "profile": "mobile", "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": { "intentToRetain": false }, "given_name": { "intentToRetain": false }, "birth_date": { "intentToRetain": false } } } } ], "credentials": [ { "docType": "org.iso.18013.5.1.mDL", "verificationResult": { "verified": false, "reason": { "type": "MobileCredentialExpired", "message": "Credential has expired" } }, "validityInfo": { "signed": "2024-01-15T10:00:00Z", "validFrom": "2024-01-15T10:00:00Z", "validUntil": "2025-01-15T10:00:00Z", "expectedUpdate": "2026-01-15T10:00:00Z" } } ] } ``` ### Presented credential verified, all claims provided [#presented-credential-verified-all-claims-provided] Both layers pass. The credential layer returns `verified: true` with no `reason`, confirming the mDL is genuine, current, and from a trusted issuer. The claims layer returns every requested claim with no `claimErrors`, so the relying party has the complete data payload it asked for. ```json title="Example response for a presented credential that verified with all requested claims returned" { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "credentialQuery": [ { "profile": "mobile", "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": { "intentToRetain": false }, "given_name": { "intentToRetain": false }, "birth_date": { "intentToRetain": false }, "address": { "intentToRetain": false } } } } ], "credentials": [ { "docType": "org.iso.18013.5.1.mDL", "claims": { "org.iso.18013.5.1": { "family_name": { "value": "Smith" }, "given_name": { "value": "Jane" }, "birth_date": { "value": "1990-05-15" }, "address": { "value": "123 Main Street, Springfield" } } }, "validityInfo": { "signed": "2024-01-15T10:00:00Z", "validFrom": "2024-01-15T10:00:00Z", "validUntil": "2027-01-15T10:00:00Z", "expectedUpdate": "2028-01-15T10:00:00Z" }, "verificationResult": { "verified": true }, "issuerInfo": { "commonName": "State Department of Motor Vehicles", "trustedIssuerId": "d4a6e9f2-3b1c-4d8e-a5f7-9c2b0e8d1a3f" }, "branding": { "name": "Driver License", "description": "State-issued driver license", "backgroundColor": "#1E3A8A", "issuerLogo": { "format": "svg", "data": "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==" } } } ] } ``` ### Presented credential verified, some claims missing [#presented-credential-verified-some-claims-missing] The credential layer passes but the claims layer is incomplete. The mDL itself returns `verified: true` and is trustworthy, while one requested claim (`portrait`) was not released and appears under `claimErrors` as `notReturned`. What is missing here is data, not trust, and the relying party's business logic must decide whether the missing claim is acceptable for its use case. ```json title="Example response for a presented credential that verified with a missing claim" { "sessionId": "550e8400-e29b-41d4-a716-446655440000", "challenge": "c5a27e4c-85b6-4b3c-9f1a-2d8e5f3a4b7c", "credentialQuery": [ { "profile": "mobile", "docType": "org.iso.18013.5.1.mDL", "nameSpaces": { "org.iso.18013.5.1": { "family_name": { "intentToRetain": false }, "given_name": { "intentToRetain": false }, "birth_date": { "intentToRetain": false }, "portrait": { "intentToRetain": false } } } } ], "credentials": [ { "docType": "org.iso.18013.5.1.mDL", "claims": { "org.iso.18013.5.1": { "family_name": { "value": "Smith" }, "given_name": { "value": "Jane" }, "birth_date": { "value": "1990-05-15" } } }, "claimErrors": { "org.iso.18013.5.1": { "portrait": "notReturned" } }, "validityInfo": { "signed": "2024-01-15T10:00:00Z", "validFrom": "2024-01-15T10:00:00Z", "validUntil": "2027-01-15T10:00:00Z", "expectedUpdate": "2028-01-15T10:00:00Z" }, "verificationResult": { "verified": true }, "issuerInfo": { "commonName": "State Department of Motor Vehicles", "trustedIssuerId": "d4a6e9f2-3b1c-4d8e-a5f7-9c2b0e8d1a3f" } } ] } ``` ## Front channel vs back channel details [#front-channel-vs-back-channel-details] ### Front channel delivery [#front-channel-delivery] When using `resultAvailableInFrontChannel: true`, the Verifier Web SDK's `requestCredentials` function returns a [`RequestCredentialsResponse`](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/types/RequestCredentialsResponse.html): ```typescript type RequestCredentialsResponse = { sessionId: string; state?: string; result?: PresentationSessionResult; sessionCompletedInRedirect?: boolean; }; ``` Where `result` contains either a `PresentationSuccessResult` or `PresentationFailureResult` as detailed above. For same-device flows, the result is delivered via `handleRedirectCallback()` after the wallet redirects the user back to your application: ```typescript type HandleRedirectCallbackResponse = { sessionId: string; state?: string; result?: PresentationSessionResult; }; ``` The optional `state` field is the caller-supplied correlation reference returned when a `state` value was provided when starting the session. See [Correlating verification sessions](/docs/verification/remote-web-verifiers/guides/correlating-verification-sessions) for details. ### Back channel delivery [#back-channel-delivery] When your backend retrieves results from the MATTR VII tenant: ``` GET /v2/presentations/sessions/{sessionId}/result ``` The response is either a `PresentationSuccessResult` or `PresentationFailureResult` with the same structure as front channel results. **Backend implementation steps**: 1. Retrieve verification results using the session ID 2. Validate the returned `challenge` matches the challenge generated before starting the session 3. Process the results according to your business logic 4. Return relevant information to your web application Challenge validation in the backend protects against session replay attacks. Generate a unique challenge when creating the session, and verify it matches when retrieving results. ## Next steps [#next-steps] * Review the [Verifier Web SDK API reference](https://api-reference-sdk.mattr.global/verifier-sdk-web/latest/) for complete type definitions * See the [presentation session result API reference](/docs/api-reference/platform/mdocs-presentation-sessions/getPresentationResult) for backend integration * Explore the [workflow guide](/docs/verification/remote-web-verifiers/workflow) for the complete end-to-end verification process * Follow the [tutorial](/docs/verification/remote-web-verifiers/tutorial) for building a remote web verifier with the MATTR VII tenant and Verifier Web SDK for practical implementation examples # Controlling wallet interactions with URI schemes URL: /docs/verification/remote-web-verifiers/guides/oid4vp-wallet-interactions Description: Learn how to control which wallet applications can interact with your verification requests using URI schemes, and how to implement different schemes for various user experience and business requirements. ## Overview [#overview] When implementing an online verification workflow using OID4VP, your MATTR VII verifier tenant generates verification request URIs that invoke wallet applications to present credentials. These request URIs can be presented to users as QR codes (for cross-device flows) or deep links (for same-device flows). As a verifier, you can control which wallet applications can interact with your verification requests and influence the user experience through URI schemes. This involves both user experience considerations and business requirements where you may want to work with specific wallet providers. ## Controlling which wallets can respond to verification requests [#controlling-which-wallets-can-respond-to-verification-requests] The mechanism for controlling which wallets can interact with your verification requests is through URI schemes and trusted wallet configurations. When you create a presentation session on your MATTR VII tenant, you can specify which wallet provider should handle the request, and MATTR VII will generate the verification request URI using that wallet's configured URI scheme. ### Understanding URI schemes [#understanding-uri-schemes] A URI scheme is the protocol part at the beginning of a URI (such as `https://`, `mailto:`, or custom schemes like `mdoc-openid4vp://`). The URI scheme you choose affects: * **Which wallet apps can respond to the request**: Some schemes allow any compatible wallet app, while others target specific wallet applications. * **User experience**: How smoothly users can present credentials and whether they see app selection prompts. * **Business alignment**: Your ability to work with specific wallet providers that align with your requirements. Several URI scheme types can be used for verification requests: 1. **ISO 18013-7 default scheme** (`mdoc-openid4vp://`): Defined by ISO/IEC 18013-7:2025. A wallet that handles this scheme declares support for the static wallet metadata parameters defined in ISO 18013-7. Any wallet app registered to handle this scheme can respond to the verification request. 2. **Private-use URI scheme** (`com.example.wallet://`): A unique custom scheme using reverse-domain notation. This makes it less likely (but not impossible) for other wallet apps to handle the request. Unlike the default schemes above, a private-use scheme carries no implied protocol configuration — you must either know the wallet's supported capabilities out-of-band, or discover them dynamically via [wallet metadata](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-wallet-metadata-authorizati). 3. **Claimed HTTPS scheme** (`https://wallet.example.com/...`): Uses domain-verified App Links (Android) or Universal Links (iOS) to ensure only the verified wallet app can handle verification requests from that domain. ### Choosing the right URI scheme [#choosing-the-right-uri-scheme] Select a URI scheme based on your requirements: | Requirement | Recommended Scheme | | ------------------------------------------------------------ | ---------------------------------------- | | ISO 18013-7 compliant wallets (e.g. mobile driving licences) | ISO 18013-7 scheme (`mdoc-openid4vp://`) | | Development and testing | ISO 18013-7 | | Target a specific wallet application | Private-use URI scheme | | Business partnerships with specific wallet providers | Private-use or Claimed HTTPS scheme | | Work with wallets that support Universal/App Links | Claimed HTTPS scheme | ### Implementing URI schemes [#implementing-uri-schemes] The following sections show you how to implement each URI scheme type as a verifier. #### ISO 18013-7 default scheme (`mdoc-openid4vp://`) [#iso-18013-7-default-scheme-mdoc-openid4vp] The `mdoc-openid4vp://` scheme is defined by ISO/IEC 18013-7:2025. By using this scheme, you signal to wallets that your verification request expects ISO 18013-7 protocol compliance — including support for ECDH-ES as `authorization_encryption_alg_values_supported`. Any wallet registered to handle this scheme can respond. **When to use:** * You are verifying ISO 18013-7 credentials (such as mobile driving licences). * You want to support any wallet that claims ISO 18013-7 compliance. * You're developing or testing an ISO 18013-7 verification flow. **How it works:** MATTR VII generates verification request URIs using this scheme when you configure a wallet provider with this authorization endpoint. **Implementation:** 1. Create a supported wallet configuration using the MATTR Portal or API (optional - this is the default behavior): 1) Expand the **Credential Verification** section in the left-hand navigation panel. 2) Select **Supported wallets**. 3) Select the **Create new** button. 4) Use the *Name* text box to insert a meaningful and friendly name for your wallet application (for example "MATTR GO Hold"). 5) Use the *Authorize endpoint* text box to insert `mdoc-openid4vp://`. 6) Select the **Create** button to create the new wallet configuration. 7) Note the wallet provider ID for this configuration, which you can use when creating presentation sessions to indicate that this is the wallet you expect to receive mDocs from. Make the following request to your MATTR VII tenant to [create a trusted wallet provider configuration](/docs/verification/remote-verification-api-reference/wallet-providers#create-a-wallet-provider): ```http title="Request" POST /v2/presentations/wallet-providers ``` ```json title="Request body" { "name": "MATTR GO Hold", // [!code focus] "openid4vpConfiguration": { "authorizationEndpoint": "mdoc-openid4vp://" // [!code focus] } } ``` *Response* ```json title="Response body" { "id": "99890c34-e4b7-4a23-84d6-e5de57114c00", // [!code focus] "name": "MATTR GO Hold", "openid4vpConfiguration": { "authorizationEndpoint": "mdoc-openid4vp://" } } ``` * `id` : We will use this value later to indicate this is the wallet the web application expects to receive mDocs from. 2. [Start a presentation session](/docs/verification/remote-web-verifiers/tutorial#create-credential-request) using the Verifier Web SDK or API, optionally referencing the wallet provider ID: ```typescript title="Verifier Web SDK example" const options: MATTRVerifierSDK.RequestCredentialsOptions = { credentialQuery: [credentialQuery], challenge: MATTRVerifierSDK.utils.generateChallenge(), openid4vpConfiguration: { redirectUri: window.location.origin, walletProviderId: "99890c34-e4b7-4a23-84d6-e5de57114c00" // Optional }, }; const results = await MATTRVerifierSDK.requestCredentials(options); ``` 3. The SDK will return a verification request URI and automatically render it as a QR code (cross-device flows) or deep link (same-device flows). **Trade-offs:** * ✅ Works with any ISO 18013-7 compliant wallet. * ✅ Standards-compliant for ISO-based credential flows. * ⚠️ Users may see multiple wallet options if they have several installed. * ⚠️ Cannot target a specific wallet application. **Requirements for wallet applications:** For wallet apps to respond to these requests, they must be configured to handle the `mdoc-openid4vp://` scheme and meet the full ISO 18013-7 protocol requirements. See the [holder guide](/docs/holding/remote-presentation-guides/handling-uri-schemes#iso-18013-7-default-scheme-mdoc-openid4vp) for implementation details. #### Private-use URI scheme [#private-use-uri-scheme] A private-use URI scheme uses reverse-domain notation (e.g., `com.example.wallet://`) to target a specific wallet application. This allows you to direct verification requests to a particular wallet provider that uses a custom URI scheme. **When to use:** * You want to work with a specific wallet provider that uses a custom URI scheme. * You're partnering with a wallet provider and want to direct users to their application. * You need better targeting than the standard scheme but the wallet doesn't support domain-verified links. **How it works:** You configure the wallet provider's custom URI scheme as their authorization endpoint on your MATTR VII tenant. When you create a presentation session and reference that wallet provider, MATTR VII generates the verification request URI using the configured scheme. **Implementation:** 1. Create a supported wallet configuration with the wallet provider's custom URI scheme. You can do this via the MATTR Portal or by making an API request: 1) Expand the **Credential Verification** section in the left-hand navigation panel. 2) Select **Supported wallets**. 3) Select the **Create new** button. 4) Use the *Name* text box to insert a meaningful and friendly name for your wallet application (for example "Partner Wallet App"). 5) Use the *Authorize endpoint* text box to insert the private-use URI scheme, for example `com.example.wallet://`. 6) Select the **Create** button to create the new wallet configuration. 7) Note the wallet provider ID for this configuration, which you can use when creating presentation sessions to indicate that this is the wallet you expect to receive mDocs from. Make the following request to your MATTR VII tenant to [create a trusted wallet provider configuration](/docs/verification/remote-verification-api-reference/wallet-providers#create-a-wallet-provider): ```http title="Request" POST /v2/presentations/wallet-providers ``` ```json title="Request body" { "name": "Partner Wallet App", // [!code focus] "openid4vpConfiguration": { "authorizationEndpoint": "com.example.wallet://" // [!code focus] } } ``` *Response* ```json title="Response body" { "id": "99890c34-e4b7-4a23-84d6-e5de57114c00", // [!code focus] "name": "Partner Wallet App", "openid4vpConfiguration": { "authorizationEndpoint": "com.example.wallet://" } } ``` * `id` : We will use this value later to indicate this is the wallet the web application expects to receive mDocs from. 2. [Start a presentation session](/docs/verification/remote-web-verifiers/tutorial#create-credential-request) and reference the wallet provider ID: ```typescript title="Verifier Web SDK example" const options: MATTRVerifierSDK.RequestCredentialsOptions = { credentialQuery: [credentialQuery], challenge: MATTRVerifierSDK.utils.generateChallenge(), openid4vpConfiguration: { redirectUri: window.location.origin, walletProviderId: "99890c34-e4b7-4a23-84d6-e5de57114c00" }, }; const results = await MATTRVerifierSDK.requestCredentials(options); ``` 3. The SDK will return a verification request URI and automatically render it as a QR code (cross-device flows) or deep link (same-device flows). **Trade-offs:** * ✅ Better targeting of a specific wallet application. * ✅ Enables partnerships with specific wallet providers. * ✅ Simple configuration on the verifier side. * ⚠️ Requires coordination with the wallet provider to know their custom scheme. * ⚠️ Users must have the specific wallet app installed. * ⚠️ Other apps could potentially register the same scheme. **Requirements for wallet applications:** The wallet app must: * Register to handle their custom URI scheme (e.g., `com.example.wallet://`). * Support the OID4VP specification for processing verification requests. * Handle the verification request URI and present credentials accordingly. Because private-use schemes carry no implied protocol configuration, you must either know the wallet's supported capabilities out-of-band, or discover them dynamically via [wallet metadata](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html#name-wallet-metadata-authorizati). See the [holder guide](/docs/holding/remote-presentation-guides/handling-uri-schemes#private-use-uri-scheme) for implementation details. #### Claimed HTTPS scheme (App Links / Universal Links) [#claimed-https-scheme-app-links--universal-links] HTTPS schemes use domain-verified App Links (Android) or Universal Links (iOS) to direct verification requests to wallet applications that support these technologies. The wallet provider must configure their domain verification to enable this. **When to use:** * You want to work with wallet providers that support Universal/App Links. * You're partnering with professional wallet providers that have domain-verified schemes. * You want the smoothest user experience with no app selection prompts for users of those wallets. **How it works:** The wallet provider configures their domain with the necessary verification files and provides their HTTPS authorization endpoint. You configure this endpoint on your MATTR VII tenant. When you create a presentation session and reference that wallet provider, MATTR VII generates the verification request URI using the HTTPS scheme, which the operating system directs to the verified wallet app. **Implementation:** 1. Obtain the wallet provider's HTTPS authorization endpoint. The wallet provider must have already set up domain verification files on their web server (see the [holder guide](/docs/holding/remote-presentation-guides/handling-uri-schemes#claimed-https-scheme-app-links--universal-links) for details on what wallet providers need to configure). 2. Create a supported wallet configuration with the wallet provider's HTTPS authorization endpoint. You can do this via the MATTR Portal or by making an API request: 1) Expand the **Credential Verification** section in the left-hand navigation panel. 2) Select **Supported wallets**. 3) Select the **Create new** button. 4) Use the *Name* text box to insert a meaningful and friendly name for your wallet application (for example "Partner Wallet with Universal Links"). 5) Use the *Authorize endpoint* text box to insert the private-use URI scheme, for example `https://wallet.example.com/verify`. 6) Select the **Create** button to create the new wallet configuration. 7) Note the wallet provider ID for this configuration, which you can use when creating presentation sessions to indicate that this is the wallet you expect to receive mDocs from. Make the following request to your MATTR VII tenant to [create a trusted wallet provider configuration](/docs/verification/remote-verification-api-reference/wallet-providers#create-a-wallet-provider): ```http title="Request" POST /v2/presentations/wallet-providers ``` ```json title="Request body" { "name": "Partner Wallet with Universal Links", // [!code focus] "openid4vpConfiguration": { "authorizationEndpoint": "https://wallet.example.com/verify" // [!code focus] } } ``` *Response* ```json title="Response body" { "id": "99890c34-e4b7-4a23-84d6-e5de57114c00", // [!code focus] "name": "Partner Wallet with Universal Links", "openid4vpConfiguration": { "authorizationEndpoint": "https://wallet.example.com/verify" } } ``` * `id` : We will use this value later to indicate this is the wallet the web application expects to receive mDocs from. 3. [Start a presentation session](/docs/verification/remote-web-verifiers/tutorial#create-credential-request) and reference the wallet provider ID: ```typescript title="Verifier Web SDK example" const options: MATTRVerifierSDK.RequestCredentialsOptions = { credentialQuery: [credentialQuery], challenge: MATTRVerifierSDK.utils.generateChallenge(), openid4vpConfiguration: { redirectUri: window.location.origin, walletProviderId: "99890c34-e4b7-4a23-84d6-e5de57114c00" }, }; const results = await MATTRVerifierSDK.requestCredentials(options); ``` 4. The SDK will return a verification request URI and automatically render it as a QR code (cross-device flows) or deep link (same-device flows). **Trade-offs:** * ✅ Smoothest user experience with no app selection prompts. * ✅ Professional appearance with branded domain links. * ✅ Works with reputable wallet providers that have domain verification. * ✅ Operating system validates the domain association. * ⚠️ Requires wallet providers to have set up domain verification. * ⚠️ Users must have the specific wallet app installed. * ⚠️ Requires coordination with wallet providers to obtain their authorization endpoint. **Requirements for wallet applications:** The wallet app must: * Have configured Associated Domains (iOS) or intent filters with `autoVerify="true"` (Android). * Host the necessary domain verification files on their web server. * Handle HTTPS URLs from their domain. * Support the OID4VP specification for processing verification requests. See the [holder guide](/docs/holding/remote-presentation-guides/handling-uri-schemes#claimed-https-scheme-app-links--universal-links) for implementation details. ### Security considerations [#security-considerations] When working with URI schemes for verification, it's important to understand the role they play in the overall security of your verification workflow: **URI schemes and user experience:** URI schemes primarily serve as a **user experience mechanism** to: * Make it easier to scan a QR code or follow a deep link and have the OS open the intended wallet app. * Reduce confusion by limiting which apps are presented as options. * For HTTPS schemes, leverage operating system domain verification to ensure a specific wallet app is the default handler. **Verification security:** The security of the verification workflow relies on the cryptographic verification of presented credentials, not on URI schemes. MATTR VII verifies: * The credential's cryptographic signature using the trusted issuer's certificate. * The credential's validity period and status. * That the presented claims match what was requested. * The device binding (for mDocs) to ensure the credential is being presented from the legitimate holder's device. These cryptographic checks ensure that even if a different wallet application responds to the verification request, the credential itself must still be legitimate and cryptographically valid. **Choosing wallet providers:** While URI schemes don't enforce which wallet can respond at a protocol level, they enable you to: * Direct users to specific wallet applications through targeted URI schemes. * Build business relationships with specific wallet providers. * Provide a better user experience by reducing ambiguity about which app to use. For stronger guarantees about which wallets can interact with your verification system, consider additional mechanisms such as wallet attestation or business agreements with wallet providers. For questions about advanced security configurations, please [contact us](mailto:dev-support@mattr.global). # Verifier Web SDK URL: /docs/verification/remote-web-verifiers/sdks/overview The Verifier Web SDK is a powerful tool for integrating online credential verification capabilities into your web applications. It enables secure and efficient verification of [mDocs](/docs/concepts/mdocs), supporting both same-device and cross-device verification workflows. The SDK leverages the MATTR VII platform to handle credential presentation and verification processes. ## SDK Capabilities [#sdk-capabilities] * Handle the verification of an mDoc via a [remote (online) presentation workflow](/docs/verification/remote-overview) as per [ISO/IEC 18013-7:2025](https://www.iso.org/standard/91154.html) and [OID4VP](https://openid.net/specs/openid-4-verifiable-presentations-1_0.html). * Support for both same-device and cross-device verification workflows. * Simple integration into web applications. * Secure handling of mDoc requests and responses. To get started with this SDK, please [contact us](mailto:sales@mattr.global) so we can work together to find the best solution for you. ## Versions [#versions] Below are the available versions of the mDocs Verifier Web SDK, including the current active version, supported versions, and those that have reached end-of-life (EOL). | Major version | Status | Latest release | End of Life date | Documentation | | ------------- | ------ | -------------- | ---------------- | ------------------------------------------------------------------------------------ | | v2 | Active | 2.2.0 | - | [SDK Docs](https://api-reference-sdk.mattr.global/verifier-sdk-web/2.2.0/index.html) | | v1 | EOL | 1.1.0 | 29-08-2025 | - | # Embedded Web App integration pattern URL: /docs/verification/remote-web-verifiers/idv-integration-patterns/embedded mDoc/mDL acceptance is typically done as part of a wider flow that includes other checks such as document scanning, facial recognition and liveness checks. Many of these standard identity verification checks are best performed on a mobile device due to the availability of built-in hardware like cameras and biometric sensors. Embedding the Verifier Web SDK capabilities within an existing IDV web application allows users to complete these verification steps seamlessly on their mobile devices while interacting with a single web application. If a user starts within a desktop browser, the assumption is that the IDV web application will securely transfer the active session to their mobile device (for example, by displaying a QR code that the user scans with their mobile browser). This ensures session continuity, allowing the user to continue the verification process on their mobile device without restarting the flow. ## Overview [#overview] * **Verification channel**: Remote, Unsupervised * **Device/s**: Same-device * **Formats**: mDocs * **Information assurance level**: Very High * **Identity assurance level**: High (exact identity assurance levels depend on specific IDV blocks implemented in the workflow). ## Journey pattern [#journey-pattern] Embedded Web App Integration Pattern ## Architecture [#architecture] Embedded Web App Architecture The following architecture flow assumes the user is now interacting with the IDV web application on their mobile device. They either started the session on mobile, or were transferred there from a desktop browser via a secure session handoff. ### Pass Challenge [#pass-challenge] The IDV back-end server generates a unique challenge related to the specific session and passes it to the IDV web app. ### Start Presentation Session [#start-presentation-session] The IDV web app uses the MATTR Verifier Web SDK to start a presentation session and convert the credential query into a signed credential request. ### Presentation Request [#presentation-request] The Verifier Web SDK passes the presentation request via the web browser to the Digital Credentials API, which surfaces matching credentials and wallets to the user. ### Presentation Response [#presentation-response] The user consents to share the requested credential data, which is returned to the Verifier Web SDK which then passes it onto the MATTR VII verifier tenant for verification. ### Credential Verification [#credential-verification] The MATTR VII verifier tenant verifies the credential presentation, including checking its digital signature against a list of trusted issuers. Once verification is completed, the verifier tenant informs the Verifier Web SDK that verification results are available. ### Fetch Verified Data [#fetch-verified-data] The IDV backend server uses the session ID and challenge to securely fetch the verified data from the MATTR VII tenant. The IDV backend can then pass verification results to the IDV web app, and continue the journey as needed. ## Detailed Journey flow [#detailed-journey-flow] The following journey begins when the options for the user to verify using an mDL is surfaced via the IDV application: 1. The IDV Web App initializes the Verifier Web SDK to prepare for credential verification. 2. As part of the IDV verification workflow, the user selects to verify their identity using an mDL. 3. The IDV Web App requests a unique challenge from the IDV Backend. This challenge will be used later to correlate the verification session. 4. The IDV Backend generates and returns a unique challenge to the IDV Web App. 5. The IDV Web App instructs the Verifier Web SDK to request credentials from the user. 6. The Verifier Web SDK requests the MATTR VII Verifier Tenant to create a new presentation session, using the unique challenge. 7. The MATTR VII Verifier Tenant generates and returns a presentation request to the Verifier Web SDK. 8. The Verifier Web SDK uses the presentation request to invoke the browser's Digital Credentials API to surface available credentials. 9. The Digital Credentials API presents matching credentials to the user. 10. The user selects which mDL credential they wish to share. 11. The user's wallet displays the detailed request information and asks for consent. 12. The user reviews the request and provides explicit consent to share the requested information from their credential. 13. The user's wallet sends the encrypted credential response to the Verifier Web SDK. 14. The Verifier Web SDK forwards the credential response to the MATTR VII Verifier Tenant. 15. The MATTR VII Verifier Tenant decrypts the response and verifies the authenticity of the credential, including checks such as signature validation, revocation status and the identity of the issuer. 16. The MATTR VII Verifier Tenant returns a session ID to the Verifier Web SDK. 17. The Verifier Web SDK informs the IDV Web App that verification results are ready. 18. The IDV Web App requests the verification results from the IDV Backend. 19. The IDV Backend retrieves the results from the MATTR VII Verifier Tenant using the session ID and challenge. This request is made via a secure server-to-server connection. 20. The MATTR VII Verifier Tenant returns the verification results to the IDV Backend. 21. The IDV Backend forwards the verification results to the IDV Web App. The IDV Web App can now correlate these results with the original challenge to ensure integrity. 22. The IDV Web App allows the user to proceed with any additional verification steps or complete the verification flow. If the user started on a different device, the assumption is that the IDV web app will inform the original browser session so the user can pick-up where they started. # Login IDV integration pattern URL: /docs/verification/remote-web-verifiers/idv-integration-patterns/login This integration pattern is used to verify a credential presented [remotely](/docs/verification/remote-web-verifiers/workflow) when attempting to log into an existing service from a different device. ## Overview [#overview] * **Verification channel**: Remote, Unsupervised * **Device/s**: Cross-device * **Formats**: mDocs * **Information assurance level**: Very High * **Identity assurance level**: High. ## Journey flow [#journey-flow] Login from additional device integration pattern ## Architecture [#architecture] Login from additional device architecture ### Interacting with the website [#interacting-with-the-website] The user is using a web browser on their desktop to access a website where they attempt to log into a service where they already have an active account. They are logged into this service on their mobile device. ### Requesting an mDL for verification [#requesting-an-mdl-for-verification] One of the options to login on their desktop browser is to present an mDL for verification. This mDL will then be compared with information stored as part of the initial account setup. This is achieved by embedding the MATTR Pi Verifier Web SDK into the web application and requesting the user to display an mDL for verification. When the user agrees to proceed, the Verifier Web SDK makes a request to a configured MATTR VII Verifier tenant. That request defines what credentials and claims are required for verification. The MATTR VII verifier tenant is configured with the following: * What domains it can accept requests from. * What workflows it supports (e.g. same-device and/or cross-device). * What wallet applications it can interact with. * How to invoke these supported wallet applications. The MATTR VII verifier tenant recognizes that the user began the interaction on a desktop browser and responds with a link that is rendered as a QR code by the Verifier Web SDK inside the web application. The user then scans that QR code with a mobile device to invoke a matching native application which includes the required mDL. ### Presenting request details to the user [#presenting-request-details-to-the-user] Once the wallet is launched, it authenticates the user and interacts with the MATTR VII tenant to retrieve and display the request details to the user: * What credentials are requested. * What claims from the credentials are requested. * Whether the relying party is vetted by the digital trust service, and whether they are allowed to request this type of information. * What matching credentials are available and can be shared with the verifier. Based on that information, the user can select to proceed with the verification workflow and share the required information with the verifier. ### Verifying the mDL [#verifying-the-mdl] The MATTR VII verifier tenant verifies the shared credentials to validate that: * The information has not been tampered with. * The credential has not been revoked (or suspended, for legacy credentials). * The credential has not expired. * The credential was issued by a trusted issuer (based on information retrieved from the DTS). ### Displaying verification results [#displaying-verification-results] The MATTR VII verifier tenant shares the verification results with the Verifier Web SDK. These results can then be compared to existing databases to verify that this is the same user who created the account. Upon successful verification, the user is logged into their account on their desktop browser. # IDV Onboarding integration pattern URL: /docs/verification/remote-web-verifiers/idv-integration-patterns/onboarding This integration pattern is used to verify a credential presented [remotely](/docs/verification/remote-web-verifiers/workflow) as part of onboarding a customer to a new service via either a [cross-device](#cross-device-flow) or [same-device](#same-device-flow) flow. ## Cross-device flow [#cross-device-flow] ### Overview [#overview] * **Verification channel**: Remote, unsupervised * **Device/s**: Cross-device (Mobile app + desktop). * **Formats**: mDocs * **Information assurance level**: Very High * **Identity assurance level**: High (exact identity assurance levels depends on specific IDV blocks implemented in the workflow). ### Integration pattern [#integration-pattern] Cross device onboarding integration pattern ### Architecture [#architecture] Enrolment architecture #### Interacting with the website [#interacting-with-the-website] The user is using a web browser on their desktop to access a website where they attempt to create a new account. This requires them to complete an IDV workflow. #### Requesting a credential for verification [#requesting-a-credential-for-verification] The first step in the IDV workflow is to present an mDL for verification. This is achieved by embedding the MATTR Pi Verifier Web SDK into the web application and the IDV workflow by requesting the user to display an mDL for verification. When the user agrees to proceed, the Verifier Web SDK makes a request to a configured MATTR VII Verifier tenant. That request defines what credentials and claims are required for verification. The MATTR VII verifier tenant is configured with the following: * What domains it can accept requests from. * What workflows it supports (e.g. same-device and/or cross-device). * What wallet applications it can interact with. * How to invoke these supported wallet applications. The MATTR VII verifier tenant recognizes that the user began the interaction on a desktop browser and responds with a link that is rendered as a QR code by the Verifier Web SDK inside the web application. The user then scans that QR code with a mobile device to invoke a matching native application which includes the required mDL. #### Presenting request details to the user [#presenting-request-details-to-the-user] Once the wallet is launched, it authenticates the user and interacts with the MATTR VII tenant to retrieve and display the request details to the user: * What credentials are requested. * What claims from the credentials are requested. * Whether the relying party is vetted by the digital trust service, and whether they are allowed to request this type of information. * What matching credentials are available and can be shared with the verifier. Based on that information, the user can select to proceed with the verification workflow and share the required information with the verifier. #### Verifying the mDL [#verifying-the-mdl] The MATTR VII verifier tenant verifies the shared credentials to validate that: * The information has not been tampered with. * The credential has not been revoked (or suspended, for legacy credentials). * The credential has not expired. * The credential was issued by a trusted issuer. #### Handling verification results [#handling-verification-results] The MATTR VII verifier tenant shares the verification results with the Verifier Web SDK. These results are then displayed to the user on their desktop browser, allowing them to continue the IDV workflow and complete any additional required steps. The IDV workflow can use information retrieved from the verified mDL (e.g. portrait image, credential claims) and compare them against any existing databases to increase the workflows’ assurance levels. This is just an example workflow. The mDL verification can be embedded in any part of the IDV journey to accommodate different workflows and requirements. ## Same-device flow [#same-device-flow] ### Overview [#overview-1] * **Issuance channel**: Remote, unsupervised * **Device/s**: Same-device (Web app to mobile app, Mobile app to mobile app). * **Formats**: mDocs * **Information assurance level**: Very High * **Identity assurance level**: High (exact identity assurance levels depends on specific IDV blocks implemented in the workflow). ### Integration pattern [#integration-pattern-1] Same device onboarding integration pattern part 1 #### Opening an account on a mobile device [#opening-an-account-on-a-mobile-device] Samantha starts the process of creating an account with a new service that demands high assurance levels (such as opening a bank account) - either in a mobile browser or a native mobile application. #### Request for identity document [#request-for-identity-document] Samantha is prompted to provide an identity document, such as a Mobile Driver's License (mDL), another verifiable mobile credential (mDoc), a physical document scan, or an alternative ID verification option. She decides to submit her mDL. #### Invoking a digital wallet [#invoking-a-digital-wallet] Samantha is redirected into a mobile application (e.g. wallet) holding the matching mDL. Same device onboarding integration pattern part 2 #### Sharing credentials [#sharing-credentials] Samantha is presented with a summary of the information from her mDL that will be shared during this process. She is then required to provide her consent and complete device authentication before proceeding with the data sharing. #### Redirect back to the website [#redirect-back-to-the-website] Samantha is redirected from the mobile application/wallet back to the mobile browser or original native mobile application, allowing her to continue with the interaction. #### Additional IDV journey blocks [#additional-idv-journey-blocks] Once her mDL is successfully verified, Samantha can proceed with the next steps in the IDV process. These may include different verification methods, such as: * Biometric check. * Liveness detection. * Call center validation. * Proof of address check. * Voice capture. * Database check. * Live video interview. * Authentication. * Physical ID validation. Same device onboarding integration pattern part 3 #### Continue with opening account [#continue-with-opening-account] Once the IDV process is complete, Samantha can move forward with opening her account. Some fields may be pre-populated with information gathered during the IDV journey, including details from the mDL verification. #### Account opened [#account-opened] Samantha successfully opens a new account with the service. ### Architecture [#architecture-1] Enrolment architecture #### Interacting with the website [#interacting-with-the-website-1] The user is using a web browser on their mobile device to access a website where they attempt to create a new account. This requires them to complete an IDV workflow. #### Requesting an mDL for verification [#requesting-an-mdl-for-verification] The first step in the IDV workflow is to present an mDL for verification. This is achieved by embedding the MATTR Pi Verifier Web SDK into the web application and the IDV workflow by requesting the user to display an mDL for verification. When the user agrees to proceed, the Verifier Web SDK makes a request to a configured MATTR VII Verifier tenant. That request defines what credentials and claims are required for verification. The MATTR VII verifier tenant is configured with the following: * What domains it can accept requests from. * What workflows it supports (e.g. same-device and/or cross-device). * What wallet applications it can interact with. * How to invoke these supported wallet applications. The MATTR VII verifier tenant recognizes that the user began the interaction on a mobile browser and responds with a link that is used by the Verifier Web SDK to redirect the user from their web browser and invoke a matching native application installed on the same mobile device and which holds the required mDL. #### Presenting request details to the user [#presenting-request-details-to-the-user-1] Once the wallet is launched, it authenticates the user and interacts with the MATTR VII tenant to retrieve and display the request details to the user: * What credentials are requested. * What claims from the credentials are requested. * Whether the relying party is vetted by the digital trust service, and whether they are allowed to request this type of information. * What matching credentials are available and can be shared with the verifier. Based on that information, the user can select to proceed with the verification workflow and share the required information. #### Verifying the mDL [#verifying-the-mdl-1] The MATTR VII verifier tenant verifies the shared credentials to validate that: * The information has not been tampered with. * The credential has not been revoked (or suspended, for legacy credentials). * The credential has not expired. * The credential was issued by a trusted issuer. #### Handling verification results [#handling-verification-results-1] The MATTR VII verifier tenant shares the verification results with the Verifier Web SDK, which redirects the user back to their browser where they can continue the IDV workflow and complete any additional required steps. The IDV workflow can use information retrieved from the verified mDL (e.g. portrait image, credential claims) and compare them against any existing databases to increase the workflows’ assurance levels. This is just an example workflow. The mDL verification can be embedded in any part of the IDV journey to accommodate different workflows and requirements. # Retrieve custom domain ## Endpoint ``` GET /v1/config/domain ``` Full URL: `https://example.vii.au01.mattr.global/v1/config/domain` ### Authorization Bearer token required. Required roles: admin, issuer, verifier, dts-provider, managed-issuer ## Description Returns your tenant's custom domain configuration and its verification status. ### **Analytic events** * CONFIG_CUSTOM_DOMAIN_RETRIEVE_START * CONFIG_CUSTOM_DOMAIN_RETRIEVE_SUCCESS * CONFIG_CUSTOM_DOMAIN_RETRIEVE_FAIL ### Responses #### 200 - Custom domain returned ```json { "name": "Example Corp", "logoUrl": "https://cdn.example.com/logo.icon", "domain": "example.com", "verificationToken": "8c6f36c1-91ff-439d-a518-48cf7ef421ef", "isVerified": false } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Configure custom domain ## Endpoint ``` POST /v1/config/domain ``` Full URL: `https://example.vii.au01.mattr.global/v1/config/domain` ### Authorization Bearer token required. Required roles: admin, issuer, verifier, dts-provider, managed-issuer ## Description Creates a custom domain configuration on your tenant. You can configure a custom domain for a specific MATTR VII tenant to represent your brand and instil trust with your end-users. Any MATTR VII tenant can only have one custom domain. Refer to our [docs](https://learn.mattr.global/docs/platform-management/custom-domain-overview) for more information. ### **Analytic events** * CONFIG_CUSTOM_DOMAIN_CREATE_START * CONFIG_CUSTOM_DOMAIN_CREATE_SUCCESS * CONFIG_CUSTOM_DOMAIN_CREATE_FAIL ### Request Body The custom domain payload ### Responses #### 201 - Custom domain created ```json { "name": "Example Corp", "logoUrl": "https://cdn.example.com/logo.icon", "domain": "example.com", "verificationToken": "8c6f36c1-91ff-439d-a518-48cf7ef421ef", "isVerified": false } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete custom domain ## Endpoint ``` DELETE /v1/config/domain ``` Full URL: `https://example.vii.au01.mattr.global/v1/config/domain` ### Authorization Bearer token required. Required roles: admin, issuer, verifier, dts-provider, managed-issuer ## Description Deletes the custom domain configuration on your tenant. Deleting your custom domain configuration breaks the linkage with any credentials issued under the custom domain. These credentials will no longer be valid. ### **Analytic events** * CONFIG_CUSTOM_DOMAIN_DELETE_START * CONFIG_CUSTOM_DOMAIN_DELETE_SUCCESS * CONFIG_CUSTOM_DOMAIN_DELETE_FAIL ### Responses #### 204 - Custom domain deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update custom domain ## Endpoint ``` PUT /v1/config/domain ``` Full URL: `https://example.vii.au01.mattr.global/v1/config/domain` ### Authorization Bearer token required. Required roles: admin, issuer, verifier, dts-provider, managed-issuer ## Description Updates the custom domain configuration. ### **Analytic events** * CONFIG_CUSTOM_DOMAIN_UPDATE_START * CONFIG_CUSTOM_DOMAIN_UPDATE_SUCCESS * CONFIG_CUSTOM_DOMAIN_UPDATE_FAIL ### Request Body ### Responses #### 200 - Custom Domain updated ```json { "name": "Example Corp", "logoUrl": "https://cdn.example.com/logo.icon", "domain": "example.com", "verificationToken": "8c6f36c1-91ff-439d-a518-48cf7ef421ef", "isVerified": false } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Verify custom domain ## Endpoint ``` POST /v1/config/domain/verify ``` Full URL: `https://example.vii.au01.mattr.global/v1/config/domain/verify` ### Authorization Bearer token required. Required roles: admin, issuer, verifier, dts-provider, managed-issuer ## Description Verifies that you have control of the configured custom domain by examining its TXT record. Your custom domain will not be active until you verify it. Refer to [Verify domain ownership](https://learn.mattr.global/docs/platform-management/custom-domain-overview#verify-domain-ownership) for more information. ### **Analytic events** * CONFIG_CUSTOM_DOMAIN_VERIFY_START * CONFIG_CUSTOM_DOMAIN_VERIFY_SUCCESS * CONFIG_CUSTOM_DOMAIN_VERIFY_FAIL ### Responses #### 204 - Custom domain verified #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Well known DID configuration ## Endpoint ``` GET /.well-known/did-configuration ``` Full URL: `https://example.vii.au01.mattr.global/.well-known/did-configuration` ### Authorization None required. ## Description Returns a list of Decentralized Identifier (DID) Configuration entries from the tenant. These are automatically created for **all** DIDS created on a tenant so that they can be used by any party aiming to establish and verify the domain-DID linkage by exposing cryptographic proofs. Thus, this endpoint is unprotected, public facing and can be deterministically found at the root of the tenant subdomain or alias by any party. Refer to [Well Known DID Configuration](https://identity.foundation/.well-known/resources/did-configuration) on the Decentralized Identity Foundation website for more information. ### Responses #### 200 - List of DID Configuration entries ```json { "entries": [ { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "type": [ "VerifiableCredential", "AlumniCredential" ], "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "issuanceDate": "2020-05-02T12:06:29.156Z", "credentialStatus": { "id": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3#1", "type": "RevocationList2020Status", "revocationListIndex": 1, "revocationListCredential": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3" }, "credentialSubject": { "givenName": "Jamie", "familyName": "Doe", "alumniOf": "Example University" }, "proof": { "type": "Ed25519Signature2018", "created": "2020-05-02T12:06:29Z", "jws": "EXAMPLE_JWS_TOKEN_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "proofPurpose": "assertionMethod", "verificationMethod": "did:web:organization.com" }, "name": "Alumni Credential", "description": "This credential shows that the person has attended the mentioned university." } ] } ``` # Retrieve a list of DIDs ## Endpoint ``` GET /v1/dids ``` Full URL: `https://example.vii.au01.mattr.global/v1/dids` ### Authorization Bearer token required. Required roles: admin, issuer, verifier, dts-provider, managed-issuer ## Description Returns a list of all DIDs (Decentralized Identifiers) managed by the tenant and their associated meta-data. ### **Analytic events** * DID_RETRIEVE_LIST_START * DID_RETRIEVE_LIST_SUCCESS * DID_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - A list of DIDs ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "did": "did:key:z6Mkt7bFYc4V2HdAxwhMtaY6cgJckYXwhYdPLJCcnVqzrkpr", "localMetadata": { "registered": 1583233799656, "keys": [ { "kmsKeyId": "ad8facc7-e7f6-4af6-9baa-2f7abd71c928", "didDocumentKeyId": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" } ] } } ] } ``` # Create a DID ## Endpoint ``` POST /v1/dids ``` Full URL: `https://example.vii.au01.mattr.global/v1/dids` ### Authorization Bearer token required. Required roles: admin, issuer, verifier, dts-provider, managed-issuer ## Description Takes a supported [DID method](https://learn.mattr.global/docs/concepts/dids#methods) and returns a new DID with its generated keys and required information. This endpoint also registers the DID Document when applicable. MATTR VII currently supports creating DIDs of the following methods: - **did:key**: The most basic type of DID. The public key forms the DID and has no further data associated with it. - **did:web**: This type of DID requires hosting the DID document on a publicly accessible domain in order to make the document and its contents available. ### **Analytic events** * DID_CREATE_START * DID_CREATE_SUCCESS * DID_CREATE_FAIL ### Request Body Options for creating the decentralized identifier ```json { "options": { "keyType": "Bls12381G2", "url": "learn.vii.au01.mattr.global" } } ``` ### Responses #### 201 - DID document created ```json { "registrationStatus": "COMPLETED", "did": "did:key:z6Mkt7bFYc4V2HdAxwhMtaY6cgJckYXwhYdPLJCcnVqzrkpr", "metadata": { "registered": 1583233799656, "keys": [ { "kmsKeyId": "ad8facc7-e7f6-4af6-9baa-2f7abd71c928", "didDocumentKeyId": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" } ] } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Resolve a DID ## Endpoint ``` GET /v1/dids/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/dids/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, verifier, dts-provider, managed-issuer ## Description Retrieves a DID and its metadata from the tenant by its URI. This may involve a network call depending on the method involved: - For did:key the public key is encapsulated in the DID URI itself. - For did:web it must be resolved by accessing the `/.well-known/did.json` path on its domain. ### **Analytic events** * DID_RETRIEVE_START * DID_RETRIEVE_SUCCESS * DID_RETRIEVE_FAIL ### Responses #### 200 - A DID Document and its meta-data ```json { "localMetadata": { "registered": 1583233799656, "keys": [ { "kmsKeyId": "ad8facc7-e7f6-4af6-9baa-2f7abd71c928", "didDocumentKeyId": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" } ] } } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a DID ## Endpoint ``` DELETE /v1/dids/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/dids/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, verifier, dts-provider, managed-issuer ## Description Deletes a DID and all associated metadata by providing its URI. This includes all the removal of all associated private keys from the Key Management System (KMS). For `did:web` you will need to manually remove the `did.json` from your hosted domain. ### **Analytic events** * DID_DELETE_START * DID_DELETE_SUCCESS * DID_DELETE_FAIL ### Responses #### 204 - DID successfully deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Sign a CWT credential ## Endpoint ``` POST /v2/credentials/compact/sign ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/sign` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a signed CWT credential generated from a provided valid payload. The payload can include any number of custom claims, as CWT credentials do not comply with any specific standard or specification. ### **Analytic events** * CREDENTIAL_COMPACT_SIGN_START * CREDENTIAL_COMPACT_SIGN_SUCCESS * CREDENTIAL_COMPACT_SIGN_FAIL ### Request Body CWT credential payload to sign ```json { "payload": { "iss": "did:web:organization.com", "nbf": 1645743759, "exp": 1646743759, "iat": 1645743759 } } ``` ### Responses #### 200 - CWT credential signed ```json { "encoded": "CSC:/1/2KCE3IQEJB5DCMSLN5KWKZABE2QFQRVDAF4CIZDJMQ5HOZLCHIYDGOJUFUYTENJNGIZTOLJVGIWTCMJQFZXGO4TPNMXGS33ENZQW2ZLEJJXWQ3QH3BAFB3LISHKGQ2KBJ6Q35NXZFD6LGZ2YIAYHZAKCF7NKTIUZUTZQ3PWDBALAWVRG5XL2H4P4WFK25X3Y5X5RTN7NOZUST67KLCEFS3EPXQU5KM7VUGOPXJLQ6K5U676PMQNWRZCZ", "decoded": { "iss": "did:web:organization.com", "nbf": 1645743759, "exp": 1646743759, "iat": 1645743759, "jti": "6tVMmKodQNaLywW6NGA2aA", "type": "CredentialType", "property1": "...", "property2": "..." } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Format a CWT credential as a QR code ## Endpoint ``` POST /v2/credentials/compact/qrcode ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/qrcode` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a QR code representation of a CWT credential from a provided encoded string representation of that credential. ### **Analytic events** * CREDENTIAL_COMPACT_QRCODE_CREATE_START * CREDENTIAL_COMPACT_QRCODE_CREATE_SUCCESS * CREDENTIAL_COMPACT_QRCODE_CREATE_FAIL ### Request Body ```json { "payload": "CSS:/1/2KCE3IQEJB5DCMSMGRKXI3IBE2QFSANKVACBUYQYB2HQKGTCDAHI6BQ2MIMA5DYBPAUWI2L...", "width": 250 } ``` ### Responses #### 200 - QR code generated #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Format a CWT credential as a PDF ## Endpoint ``` POST /v2/credentials/compact/pdf ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/pdf` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a PDF representation of a provided CWT credential based on an existing PDF template. The request will fail if the provided credential isn't valid or has expired. ### **Analytic events** * CREDENTIAL_COMPACT_PDF_CREATE_START * CREDENTIAL_COMPACT_PDF_CREATE_SUCCESS * CREDENTIAL_COMPACT_PDF_CREATE_FAIL ### Request Body Credential payload ```json { "templateId": "4eea7654-d4c5-4eba-bd7a-5ca334d54725", "payload": "{payload}" } ``` ### Responses #### 200 - PDF created #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Format a CWT credential as an Apple Pass ## Endpoint ``` POST /v2/credentials/compact/digital-pass/apple ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/apple` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns an Apple Pass representation of a provided CWT credential based on an existing Apple Pass template. The request will fail if the provided credential isn't valid or has expired. ### **Analytic events** * CREDENTIAL_COMPACT_APPLE_PASS_CREATE_START * CREDENTIAL_COMPACT_APPLE_PASS_CREATE_SUCCESS * CREDENTIAL_COMPACT_APPLE_PASS_CREATE_FAIL ### Request Body ```json { "templateId": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "payload": "{payload}" } ``` ### Responses #### 200 - Apple Pass created #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Format a CWT credential as a Google Pass ## Endpoint ``` POST /v2/credentials/compact/digital-pass/google ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/google` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a Google Pass representation of a provided CWT credential based on an existing Google Pass template. The request will fail if the provided credential isn't valid or has expired. ### **Analytic events** * CREDENTIAL_COMPACT_GOOGLE_PASS_CREATE_START * CREDENTIAL_COMPACT_GOOGLE_PASS_CREATE_SUCCESS * CREDENTIAL_COMPACT_GOOGLE_PASS_CREATE_FAIL ### Request Body ```json { "templateId": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "payload": "{payload}" } ``` ### Responses #### 200 - Google Pass created ```json { "redirectTo": "https://pay.google.com/gp/v/save/{jwt}" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete CWT credential metadata ## Endpoint ``` DELETE /v2/credentials/compact/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes all credential metadata from the tenant for a specific credential by providing its ID. If the credential was set to be revocable, it will be permanently revoked upon metadata deletion. Note that only metadata of revocable credentials or credentials issued via the OID4VCI flow is saved. Deleted metadata cannot be recovered. ### Responses #### 204 - Credential metadata deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all CWT credential revocation lists ## Endpoint ``` GET /v2/credentials/compact/revocation-lists ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/revocation-lists` ### Authorization None required. ## Description Returns a list of all CWT credential revocation lists on the tenant. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * CREDENTIAL_COMPACT_REVOCATION_LISTS_RETRIEVE_START * CREDENTIAL_COMPACT_REVOCATION_LISTS_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_REVOCATION_LISTS_RETRIEVE_FAIL ### Responses #### 200 - Revocation lists retrieved # Retrieve CWT credential revocation list ## Endpoint ``` GET /v2/credentials/compact/revocation-lists/{listId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/revocation-lists/{listId}` ### Authorization None required. ## Description Returns a CWT credential revocation list by providing its ID. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * CREDENTIAL_COMPACT_REVOCATION_RETRIEVE_START * CREDENTIAL_COMPACT_REVOCATION_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_REVOCATION_RETRIEVE_FAIL ### Responses #### 200 - Revocation list retrieved # Retrieve CWT credential revocation status ## Endpoint ``` GET /v2/credentials/compact/{id}/revocation-status ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/{id}/revocation-status` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieve the revocation status of a CWT credential by providing its ID. ### Responses #### 200 - Revocation status retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update CWT credential revocation status ## Endpoint ``` POST /v2/credentials/compact/{id}/revocation-status ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/{id}/revocation-status` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates the credential status as revoked (invalid) or unrevoked (valid). ### **Analytic events** * CREDENTIAL_COMPACT_REVOCATION_SET_STATUS_START * CREDENTIAL_COMPACT_REVOCATION_SET_STATUS_SUCCESS * CREDENTIAL_COMPACT_REVOCATION_SET_STATUS_FAIL ### Request Body Update revocation status ### Responses #### 200 - Revocation status updated #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Verify a CWT credential ## Endpoint ``` POST /v2/credentials/compact/verify ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/verify` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Verify a CWT credential by providing the encoded payload and specifying verification options. You can provide a valid CWT credential as either an encoded string or a QR code in a PDF document or an image file. Standard checks performed on all verification requests: - Conformance of the string and encoded data. All string representations of CWT credentials must be prefixed with `CSC/1`. - Decoded payload structure is a valid CWT credential. - Issuer DID can be used to resolve its `did.json` document. - Public key from issuer's `did.json` document validates the proof signature, confirming the credential has not been tampered with. Optional parameter checks: - Credential was issued by a trusted issuer. - Current time is after the beginning of the credential validity period. - Current time is not after the end of the credential validity period. - Credential has not been revoked. ### **Analytic events** * CREDENTIAL_COMPACT_VERIFY_START * CREDENTIAL_COMPACT_VERIFY_SUCCESS * CREDENTIAL_COMPACT_VERIFY_FAIL ### Request Body ```json { "payload": "CSC:/1/2KCE3IQEJB5DCMSLN5KWKZABE2QFQRVDAF4CIZDJMQ5HOZLCHIYDGOJUFUYTENJNGIZTOLJVGIWTCMJQFZXGO4TPNMXGS33ENZQW2ZLEJJXWQ3QH3BAFB3LISHKGQ2KBJ6Q35NXZFD6LGZ2YIAYHZAKCF7NKTIUZUTZQ3PWDBALAWVRG5XL2H4P4WFK25X3Y5X5RTN7NOZUST67KLCEFS3EPXQU5KM7VUGOPXJLQ6K5U676PMQNWRZCZ", "trustedIssuers": [ "did:web:organization.com" ] } ``` ### Responses #### 200 - Verification completed ```json { "decoded": { "iss": "did:web:organization.com", "nbf": 1645743759, "exp": 1646743759, "iat": 1645743759, "jti": "6tVMmKodQNaLywW6NGA2aA", "type": "CredentialType", "property1": "...", "property2": "..." } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 413 - Payload Too Large #### 415 - Unsupported Media Type # Retrieve all CWT credential configurations ## Endpoint ``` GET /v2/credentials/compact/configurations ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/configurations` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a list of all CWT credential configurations from your tenant. ### **Analytic events** * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_START * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_SUCCESS * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. - `type`: string The optional credential type to filter on ### Responses #### 200 - CWT credential configurations retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "983c0a86-204f-4431-9371-f5a22e506599", "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a CWT credential configuration ## Endpoint ``` POST /v2/credentials/compact/configurations ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/configurations` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Creates a new CWT credential configuration, a specific set of rules and parameters that are used to create and validate a particular type of verifiable credential. These rules and parameters define how the credential is structured and what data it contains when issued. ### **Analytic events** * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_CREATE_START * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_CREATE_SUCCESS * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_CREATE_FAIL ### Request Body The credential configuration payload ```json { "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ``` ### Responses #### 201 - CWT credential configuration created ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a CWT credential configuration ## Endpoint ``` GET /v2/credentials/compact/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a CWT credential configuration by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_RETRIEVE_START * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_RETRIEVE_FAIL ### Responses #### 200 - CWT credential configuration retrieved ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a CWT credential configuration ## Endpoint ``` DELETE /v2/credentials/compact/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Deletes an existing CWT credential configuration by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_DELETE_START * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_DELETE_SUCCESS * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_DELETE_FAIL ### Responses #### 204 - CWT credential configuration deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a CWT credential configuration ## Endpoint ``` PUT /v2/credentials/compact/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Updates an existing CWT credential configuration by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_UPDATE_START * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_UPDATE_SUCCESS * CREDENTIAL_COMPACT_CREDENTIAL_CONFIGURATION_UPDATE_FAIL ### Request Body Update a CWT credential configuration ```json { "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ``` ### Responses #### 200 - CWT credential configuration updated ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Sign a Semantic CWT credential ## Endpoint ``` POST /v2/credentials/compact-semantic/sign ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/sign` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a signed Semantic CWT credential generated from a provided valid payload. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_SIGN_START * CREDENTIAL_COMPACT_SEMANTIC_SIGN_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_SIGN_FAIL ### Request Body Semantic CWT credential payload to sign ```json { "payload": { "iss": "did:web:organization.com", "nbf": 1645743759, "exp": 1645743759, "iat": 1645743759, "aud": "...", "sub": "...", "vc": { "@context": "https://www.w3.org/2018/credentials/examples/v1", "type": "AlumniCredential" } } } ``` ### Responses #### 200 - Semantic CWT credential signed ```json { "encoded": "CSS:/1/BASE_32_ENCODED_PAYLOAD", "decoded": { "iss": "did:web:example.com", "jti": "...", "nbf": 1645743759, "exp": 1645743759, "iat": 1645743759, "aud": "...", "sub": "...", "vc": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "type": [ "VerifiableCredential", "AlumniCredential" ] }, "status": { "url": "...", "index": 123 } } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Format a Semantic CWT credential as a QR code ## Endpoint ``` POST /v2/credentials/compact-semantic/qrcode ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/qrcode` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a QR code representation of a Semantic CWT credential from a provided encoded string representation of that credential. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_QRCODE_CREATE_START * CREDENTIAL_COMPACT_SEMANTIC_QRCODE_CREATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_QRCODE_CREATE_FAIL ### Request Body ```json { "payload": "CSS:/1/2KCE3IQEJB5DCMSMGRKXI3IBE2QFSANKVACBUYQYB2HQKGTCDAHI6BQ2MIMA5DYBPAUWI2L...", "width": 250 } ``` ### Responses #### 200 - QR code generated #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Format a CWT credential as a PDF ## Endpoint ``` POST /v2/credentials/compact-semantic/pdf ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/pdf` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a PDF representation of a provided CWT credential based on an existing PDF template. The request will fail if the provided credential isn't valid or has expired. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_PDF_CREATE_START * CREDENTIAL_COMPACT_SEMANTIC_PDF_CREATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_PDF_CREATE_FAIL ### Request Body The credential payload ```json { "templateId": "4eea7654-d4c5-4eba-bd7a-5ca334d54725", "payload": "{payload}" } ``` ### Responses #### 200 - PDF created #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Format a Semantic CWT credential as an Apple Pass ## Endpoint ``` POST /v2/credentials/compact-semantic/digital-pass/apple ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/apple` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns an Apple Pass representation of a provided Semantic CWT credential based on an existing Apple Pass template. The request will fail if the provided credential isn't valid or has expired. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_CREATE_START * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_CREATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_CREATE_FAIL ### Request Body ```json { "templateId": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "payload": "{payload}" } ``` ### Responses #### 200 - Apple Pass created #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Format a Semantic CWT credential as a Google Pass ## Endpoint ``` POST /v2/credentials/compact-semantic/digital-pass/google ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/google` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a Google Pass representation of a provided CWT credential based on an existing Google Pass template. The request will fail if the provided credential isn't valid or has expired. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_CREATE_START * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_CREATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_CREATE_FAIL ### Request Body ```json { "templateId": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "payload": "{payload}" } ``` ### Responses #### 200 - Google Pass created ```json { "redirectTo": "https://pay.google.com/gp/v/save/{jwt}" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Verify a Semantic CWT credential ## Endpoint ``` POST /v2/credentials/compact-semantic/verify ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/verify` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Verify a Semantic CWT credential by providing the encoded payload and specifying verification options. You can provide a valid Semantic CWT credential as either an encoded string or a QR code in a PDF document or an image file. Standard checks performed on all verification requests: - Conformance of the string and encoded data. All string representations of CWT credentials must be prefixed with `CSC/1`. - Decoded payload CWT structure and attributes can be validated. - Remote context schema can be resolved and validate claims can be dereferenced. - Issuer DID can be used to resolve its `did.json` document. - Public key from issuer's `did.json` document validates the proof signature, confirming the credential has not been tampered with. Optional parameter checks: - Credential was issued by a trusted issuer. - Current time is after the beginning of the credential validity period. - Current time is not after the end of the credential validity period. - Credential has not been revoked. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_VERIFY_START * CREDENTIAL_COMPACT_SEMANTIC_VERIFY_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_VERIFY_FAIL ### Request Body ```json { "payload": "CSS:/1/2KCE3IQEJB5DCMSLN5KWKZABE2QFRMFEAF4CIZDJMQ5HOZLCHIYDGOJUFUYTENJNGIZTOLJVGIWTCMJQFZXGO4TPNMXGS33COZR2G2CAMNXW45DFPB2IC6BGNB2HI4DTHIXS653XO4XHOMZON5ZGOLZSGAYTQL3DOJSWIZLOORUWC3DTF53DCZDUPFYGLALUKZSXE2LGNFQWE3DFINZGKZDFNZ2GSYLMOFRXEZLEMVXHI2LBNRJXKYTKMVRXJILENZQW2ZLEJJXWQ3QH3BAFAW2MIRFQDICFCSNL5EIX4IISCEIFDJRFHCRRLBALWFYDLUVEKXHERNWHUDGJI3DDNNXSFWIRHUASBHGB2I7UHGPZMJEB3SMOFMBL3PABL5HUFSQLLGNE7YRKSAM3OAQN7F4LG365HL67BU", "trustedIssuers": [ "did:web:example.com" ] } ``` ### Responses #### 200 - Verification completed ```json { "decoded": { "iss": "did:web:example.com", "jti": "...", "nbf": 1645743759, "exp": 1645743759, "iat": 1645743759, "aud": "...", "sub": "...", "vc": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "type": [ "VerifiableCredential", "AlumniCredential" ] }, "status": { "url": "...", "index": 123 } } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 413 - Payload Too Large #### 415 - Unsupported Media Type # Delete Semantic CWT credential metadata ## Endpoint ``` DELETE /v2/credentials/compact-semantic/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes all credential metadata from the tenant for a specific credential by providing its ID. If the credential was set to be revocable, it will be permanently revoked upon metadata deletion. Note that only metadata of revocable credentials or credentials issued via the OID4VCI flow is saved. Deleted metadata cannot be recovered. ### Responses #### 204 - Credential metadata deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Semantic CWT credential revocation lists ## Endpoint ``` GET /v2/credentials/compact-semantic/revocation-lists ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/revocation-lists` ### Authorization None required. ## Description Returns a list of all Semantic CWT credential revocation lists on the tenant. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_REVOCATION_LISTS_RETRIEVE_START * CREDENTIAL_COMPACT_SEMANTIC_REVOCATION_LISTS_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_REVOCATION_LISTS_RETRIEVE_FAIL ### Responses #### 200 - Revocation lists retrieved # Retrieve Semantic CWT credential revocation list ## Endpoint ``` GET /v2/credentials/compact-semantic/revocation-lists/{listId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/revocation-lists/{listId}` ### Authorization None required. ## Description Returns a Semantic CWT credential revocation list by providing its ID. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_REVOCATION_RETRIEVE_START * CREDENTIAL_COMPACT_SEMANTIC_REVOCATION_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_REVOCATION_RETRIEVE_FAIL ### Responses #### 200 - Revocation list retrieved # Retrieve Semantic CWT credential revocation status ## Endpoint ``` GET /v2/credentials/compact-semantic/{id}/revocation-status ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/{id}/revocation-status` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieve the revocation status of a Semantic CWT credential by providing its ID. ### Responses #### 200 - Revocation status retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update Semantic CWT credential revocation status ## Endpoint ``` POST /v2/credentials/compact-semantic/{id}/revocation-status ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/{id}/revocation-status` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates the credential status as revoked (invalid) or unrevoked (valid). ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_REVOCATION_SET_STATUS_START * CREDENTIAL_COMPACT_SEMANTIC_REVOCATION_SET_STATUS_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_REVOCATION_SET_STATUS_FAIL ### Request Body Update revocation status ### Responses #### 200 - Revocation status updated #### 404 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Semantic CWT credentials configurations ## Endpoint ``` GET /v2/credentials/compact-semantic/configurations ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/configurations` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a list of all Compact Semantic Credential configurations from your tenant. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_START * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. - `type`: string The optional credential type to filter on ### Responses #### 200 - Semantic CWT credentials configurations retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "983c0a86-204f-4431-9371-f5a22e506599", "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a Semantic CWT credentials configuration ## Endpoint ``` POST /v2/credentials/compact-semantic/configurations ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/configurations` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Creates a new Semantic CWT credentials configuration, a specific set of rules and parameters that are used to create and validate a particular type of verifiable credential. These rules and parameters define how the credential is structured and what data it contains when issued. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_CREATE_START * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_CREATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_CREATE_FAIL ### Request Body The Credential Configuration payload ```json { "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ``` ### Responses #### 201 - Credential configuration created ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a Semantic CWT credentials configuration ## Endpoint ``` GET /v2/credentials/compact-semantic/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a Semantic CWT credentials configuration by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_START * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_FAIL ### Responses #### 200 - Semantic CWT credentials configuration retrieved ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a Semantic CWT credentials configuration ## Endpoint ``` DELETE /v2/credentials/compact-semantic/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Deletes an existing Semantic CWT credentials configuration by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_DELETE_START * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_DELETE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_DELETE_FAIL ### Responses #### 204 - Semantic CWT credentials configuration deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a Semantic CWT credentials configuration ## Endpoint ``` PUT /v2/credentials/compact-semantic/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Updates an existing Semantic CWT credentials configuration by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_UPDATE_START * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_UPDATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_CREDENTIAL_CONFIGURATION_UPDATE_FAIL ### Request Body Update a Credential Configuration ```json { "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ``` ### Responses #### 200 - Semantic CWT credentials configuration updated ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "type": "CourseCredential", "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "years": 1, "months": 12, "weeks": 52, "days": 365, "hours": 24, "minutes": 1440, "seconds": 3600 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all PDF templates ## Endpoint ``` GET /v2/credentials/compact/pdf/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/pdf/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves a list of all CWT credentials PDF templates available on the tenant. ### **Analytic events** * CREDENTIAL_COMPACT_PDF_TEMPLATE_RETRIEVE_LIST_START * CREDENTIAL_COMPACT_PDF_TEMPLATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_COMPACT_PDF_TEMPLATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - PDF templates retrieved ```json { "nextCursor": "0ecdcb57-ef2b-4aa1-be34-695c2d9d9486", "data": [ { "id": "4eea7654-d4c5-4eba-bd7a-5ca334d54725", "name": "Certificate of participation", "fileName": "certificate_of_participation", "fonts": [ { "name": "PublicSans-Regular", "fileName": "fonts/PublicSans-Regular.ttf" } ], "metadata": { "title": "" }, "fields": [ { "key": "familyName", "value": "{{payload.sub_claims.familyName}}", "isRequired": true, "alternativeText": "{{payload.sub_claims.familyName}}", "fontName": "PublicSans-Regular" } ] } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a PDF template ## Endpoint ``` POST /v2/credentials/compact/pdf/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/pdf/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Creates a CWT credential PDF template based on a provided `.zip` file. Refer to our [PDF template design guide](https://learn.mattr.global/docs/issuance/cwt-credential-templates/pdf-templates) for more information on how to design a template and structure the `.zip` file. ### **Analytic events** * CREDENTIAL_COMPACT_PDF_TEMPLATE_CREATE_START * CREDENTIAL_COMPACT_PDF_TEMPLATE_CREATE_SUCCESS * CREDENTIAL_COMPACT_PDF_TEMPLATE_CREATE_FAIL ### Request Body ### Responses #### 200 - PDF template created ```json { "id": "4eea7654-d4c5-4eba-bd7a-5ca334d54725", "name": "Certificate of participation", "fileName": "certificate_of_participation", "fonts": [ { "name": "PublicSans-Regular", "fileName": "fonts/PublicSans-Regular.ttf" } ], "metadata": { "title": "" }, "fields": [ { "key": "familyName", "value": "{{payload.sub_claims.familyName}}", "isRequired": true, "alternativeText": "{{payload.sub_claims.familyName}}", "fontName": "PublicSans-Regular" } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a PDF template ## Endpoint ``` GET /v2/credentials/compact/pdf/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/pdf/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing CWT credentials PDF template using its ID. ### **Analytic events** * CREDENTIAL_COMPACT_PDF_TEMPLATE_RETRIEVE_START * CREDENTIAL_COMPACT_PDF_TEMPLATE_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_PDF_TEMPLATE_RETRIEVE_FAIL ### Path Parameters - `id`: string (required) PDF template ID ### Responses #### 200 - PDF template retrieved ```json { "id": "4eea7654-d4c5-4eba-bd7a-5ca334d54725", "name": "Certificate of participation", "fileName": "certificate_of_participation", "fonts": [ { "name": "PublicSans-Regular", "fileName": "fonts/PublicSans-Regular.ttf" } ], "metadata": { "title": "" }, "fields": [ { "key": "familyName", "value": "{{payload.sub_claims.familyName}}", "isRequired": true, "alternativeText": "{{payload.sub_claims.familyName}}", "fontName": "PublicSans-Regular" } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a PDF template ## Endpoint ``` DELETE /v2/credentials/compact/pdf/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/pdf/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Delete an existing PDF template by providing its ID ### **Analytic events** * CREDENTIAL_COMPACT_PDF_TEMPLATE_DELETE_START * CREDENTIAL_COMPACT_PDF_TEMPLATE_DELETE_SUCCESS * CREDENTIAL_COMPACT_PDF_TEMPLATE_DELETE_FAIL ### Path Parameters - `id`: string (required) PDF Template ID ### Responses #### 204 - PDF template deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a PDF template ## Endpoint ``` PUT /v2/credentials/compact/pdf/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/pdf/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Update an existing CWT credential PDF template based on a provided `.zip` file. Refer to our [PDF template design guide](https://learn.mattr.global/docs/issuance/cwt-credential-templates/pdf-templates) for more information on how to design a template and structure the `.zip` file. ### **Analytic events** * CREDENTIAL_COMPACT_PDF_TEMPLATE_UPDATE_START * CREDENTIAL_COMPACT_PDF_TEMPLATE_UPDATE_SUCCESS * CREDENTIAL_COMPACT_PDF_TEMPLATE_UPDATE_FAIL ### Path Parameters - `id`: string (required) PDF template ID ### Request Body ### Responses #### 200 - PDF template updated ```json { "id": "4eea7654-d4c5-4eba-bd7a-5ca334d54725", "name": "Certificate of participation", "fileName": "certificate_of_participation", "fonts": [ { "name": "PublicSans-Regular", "fileName": "fonts/PublicSans-Regular.ttf" } ], "metadata": { "title": "" }, "fields": [ { "key": "familyName", "value": "{{payload.sub_claims.familyName}}", "isRequired": true, "alternativeText": "{{payload.sub_claims.familyName}}", "fontName": "PublicSans-Regular" } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all PDF templates ## Endpoint ``` GET /v2/credentials/compact-semantic/pdf/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/pdf/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves a list of all Semantic CWT credential PDF templates available on the tenant. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_RETRIEVE_LIST_START * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - PDF templates retrieved ```json { "nextCursor": "0ecdcb57-ef2b-4aa1-be34-695c2d9d9486", "data": [ { "id": "4eea7654-d4c5-4eba-bd7a-5ca334d54725", "name": "Certificate of participation", "fileName": "certificate_of_participation", "fonts": [ { "name": "PublicSans-Regular", "fileName": "fonts/PublicSans-Regular.ttf" } ], "metadata": { "title": "" }, "fields": [ { "key": "familyName", "value": "{{payload.sub_claims.familyName}}", "isRequired": true, "alternativeText": "{{payload.sub_claims.familyName}}", "fontName": "PublicSans-Regular" } ] } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a PDF template ## Endpoint ``` POST /v2/credentials/compact-semantic/pdf/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/pdf/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Creates a Semantic CWT credential PDF template based on a provided `.zip` file. Refer to our [PDF template design guide](https://learn.mattr.global/docs/issuance/cwt-credential-templates/pdf-templates) for more information on how to design a template and structure the `.zip` file. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_CREATE_START * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_CREATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_CREATE_FAIL ### Request Body ### Responses #### 200 - PDF template created ```json { "id": "4eea7654-d4c5-4eba-bd7a-5ca334d54725", "name": "Certificate of participation", "fileName": "certificate_of_participation", "fonts": [ { "name": "PublicSans-Regular", "fileName": "fonts/PublicSans-Regular.ttf" } ], "metadata": { "title": "" }, "fields": [ { "key": "familyName", "value": "{{payload.sub_claims.familyName}}", "isRequired": true, "alternativeText": "{{payload.sub_claims.familyName}}", "fontName": "PublicSans-Regular" } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a PDF template ## Endpoint ``` GET /v2/credentials/compact-semantic/pdf/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/pdf/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing Semantic CWT credential PDF template using its ID. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_RETRIEVE_START * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_RETRIEVE_FAIL ### Path Parameters - `id`: string (required) PDF Template ID ### Responses #### 200 - PDF template retrieved ```json { "id": "4eea7654-d4c5-4eba-bd7a-5ca334d54725", "name": "Certificate of participation", "fileName": "certificate_of_participation", "fonts": [ { "name": "PublicSans-Regular", "fileName": "fonts/PublicSans-Regular.ttf" } ], "metadata": { "title": "" }, "fields": [ { "key": "familyName", "value": "{{payload.sub_claims.familyName}}", "isRequired": true, "alternativeText": "{{payload.sub_claims.familyName}}", "fontName": "PublicSans-Regular" } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a PDF template ## Endpoint ``` DELETE /v2/credentials/compact-semantic/pdf/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/pdf/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Delete an existing PDF template by providing its ID ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_DELETE_START * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_DELETE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_DELETE_FAIL ### Path Parameters - `id`: string (required) PDF Template ID ### Responses #### 204 - PDF template deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a PDF template ## Endpoint ``` PUT /v2/credentials/compact-semantic/pdf/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/pdf/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Update an existing CWT credential PDF template based on a provided `.zip` file. Refer to our [PDF template design guide](https://learn.mattr.global/docs/issuance/cwt-credential-templates/pdf-templates) for more information on how to design a template and structure the `.zip` file. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_UPDATE_START * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_UPDATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_PDF_TEMPLATE_UPDATE_FAIL ### Path Parameters - `id`: string (required) PDF Template ID ### Request Body ### Responses #### 200 - PDF template updated ```json { "id": "4eea7654-d4c5-4eba-bd7a-5ca334d54725", "name": "Certificate of participation", "fileName": "certificate_of_participation", "fonts": [ { "name": "PublicSans-Regular", "fileName": "fonts/PublicSans-Regular.ttf" } ], "metadata": { "title": "" }, "fields": [ { "key": "familyName", "value": "{{payload.sub_claims.familyName}}", "isRequired": true, "alternativeText": "{{payload.sub_claims.familyName}}", "fontName": "PublicSans-Regular" } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Apple Pass templates ## Endpoint ``` GET /v2/credentials/compact/digital-pass/apple/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/apple/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves a list of all Apple Pass template available on the tenant. ### **Analytic events** * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_RETRIEVE_LIST_START * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Apple Pass templates retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1h", "data": [ { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "fileName": "certificate_of_participation.pkpass", "teamIdentifier": "GH5P43ABC", "passTypeIdentifier": "pass.myproject.participation.pk" } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create an Apple Pass template ## Endpoint ``` POST /v2/credentials/compact/digital-pass/apple/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/apple/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Creates an Apple Pass template based on the provided `.zip` file. Refer to our [Design an Apple Pass template](https://learn.mattr.global/docs/issuance/cwt-credential-templates/apple-templates) guide for more information on how to design the template and how to structure the `.zip` file. The Apple Pass template uses the official Apple Pass bundle structure. ### **Analytic events** * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_CREATE_START * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_CREATE_SUCCESS * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_CREATE_FAIL ### Request Body ### Responses #### 201 - Apple Pass template created ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "fileName": "certificate_of_participation.pkpass", "teamIdentifier": "GH5P43ABC", "passTypeIdentifier": "pass.myproject.participation.pk" } } ``` #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve an Apple Pass template ## Endpoint ``` GET /v2/credentials/compact/digital-pass/apple/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/apple/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing Apple Pass template by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_RETRIEVE_START * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_RETRIEVE_FAIL ### Path Parameters - `id`: string (required) Apple Pass template ID ### Responses #### 200 - Apple Pass template retrieved ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "fileName": "certificate_of_participation.pkpass", "teamIdentifier": "GH5P43ABC", "passTypeIdentifier": "pass.myproject.participation.pk" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete an Apple Pass template ## Endpoint ``` DELETE /v2/credentials/compact/digital-pass/apple/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/apple/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes an existing Apple Pass template by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_DELETE_START * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_DELETE_SUCCESS * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_DELETE_FAIL ### Path Parameters - `id`: string (required) Apple Pass template ID ### Responses #### 204 - Apple Pass template deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update an Apple Pass template ## Endpoint ``` PUT /v2/credentials/compact/digital-pass/apple/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/apple/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates an existing Apple Pass template by providing its ID and a `.zip` file. Refer to our [Design an Apple Pass template](https://learn.mattr.global/docs/issuance/cwt-credential-templates/apple-templates) guide for more information on how to design the template and how to structure the `.zip` file. ### **Analytic events** * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_UPDATE_START * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_UPDATE_SUCCESS * CREDENTIAL_COMPACT_APPLE_PASS_TEMPLATE_UPDATE_FAIL ### Path Parameters - `id`: string (required) Apple Pass template ID ### Request Body ### Responses #### 200 - Apple Pass template updated ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "fileName": "certificate_of_participation.pkpass", "teamIdentifier": "GH5P43ABC", "passTypeIdentifier": "pass.myproject.participation.pk" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Google Pass templates ## Endpoint ``` GET /v2/credentials/compact/digital-pass/google/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/google/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves a list of all Google Pass templates available on your tenant. ### **Analytic events** * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_RETRIEVE_LIST_START * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Google Pass templates retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1h", "data": [ { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "issuerId": "3388000000012346000", "serviceAccountClientEmail": "app-user@myproject.iam.gserviceaccount.com", "payPassId": "3388000000012345678.a0bbe92f-c85e-4081-94c3-f842bcd5e463" } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a Google Pass template ## Endpoint ``` POST /v2/credentials/compact/digital-pass/google/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/google/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Creates a Google Pass template based on the provided `.zip` file. Refer to our [Design a Google Pass template](https://learn.mattr.global/docs/issuance/cwt-credential-templates/google-templates) guide for more information on how to design the template and how to structure the `.zip` file. ### **Analytic events** * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_CREATE_START * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_CREATE_SUCCESS * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_CREATE_FAIL ### Request Body ### Responses #### 201 - Google Pass template created ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "issuerId": "3388000000012346000", "serviceAccountClientEmail": "app-user@myproject.iam.gserviceaccount.com", "payPassId": "3388000000012345678.a0bbe92f-c85e-4081-94c3-f842bcd5e463" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a Google Pass template ## Endpoint ``` GET /v2/credentials/compact/digital-pass/google/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/google/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing Google Pass template by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_RETRIEVE_START * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_RETRIEVE_FAIL ### Path Parameters - `id`: string (required) Google Pass template ID ### Responses #### 200 - Google Pass templated retrieved ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "issuerId": "3388000000012346000", "serviceAccountClientEmail": "app-user@myproject.iam.gserviceaccount.com", "payPassId": "3388000000012345678.a0bbe92f-c85e-4081-94c3-f842bcd5e463" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a Google Pass template ## Endpoint ``` DELETE /v2/credentials/compact/digital-pass/google/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/google/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes an existing Google Pass template by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_DELETE_START * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_DELETE_SUCCESS * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_DELETE_FAIL ### Path Parameters - `id`: string (required) Google Pass template ID ### Responses #### 204 - Google Pass template deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a Google Pass template ## Endpoint ``` PUT /v2/credentials/compact/digital-pass/google/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact/digital-pass/google/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates a existing Google Pass template by providing its ID and a `.zip` file. Refer to our [Design a Google Pass template](https://learn.mattr.global/docs/issuance/cwt-credential-templates/google-templates) guide for more information on how to design the template and how to structure the `.zip` file. ### **Analytic events** * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_UPDATE_START * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_UPDATE_SUCCESS * CREDENTIAL_COMPACT_GOOGLE_PASS_TEMPLATE_UPDATE_FAIL ### Path Parameters - `id`: string (required) Google Pass template ID ### Request Body ### Responses #### 200 - Google Pass template updated ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "issuerId": "3388000000012346000", "serviceAccountClientEmail": "app-user@myproject.iam.gserviceaccount.com", "payPassId": "3388000000012345678.a0bbe92f-c85e-4081-94c3-f842bcd5e463" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Apple Pass templates ## Endpoint ``` GET /v2/credentials/compact-semantic/digital-pass/apple/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/apple/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves a list of all Apple Pass template available on the tenant. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_RETRIEVE_LIST_START * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Apple Pass templates retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1h", "data": [ { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "fileName": "certificate_of_participation.pkpass", "teamIdentifier": "GH5P43ABC", "passTypeIdentifier": "pass.myproject.participation.pk" } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create an Apple Pass template ## Endpoint ``` POST /v2/credentials/compact-semantic/digital-pass/apple/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/apple/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Creates an Apple Pass template based on the provided `.zip` file. Refer to our [Design an Apple Pass template](https://learn.mattr.global/docs/issuance/cwt-credential-templates/apple-templates) guide for more information on how to design the template and how to structure the `.zip` file. The Apple Pass template uses the official Apple Pass bundle structure. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_CREATE_START * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_CREATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_CREATE_FAIL ### Request Body ### Responses #### 201 - Apple Pass template created ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "fileName": "certificate_of_participation.pkpass", "teamIdentifier": "GH5P43ABC", "passTypeIdentifier": "pass.myproject.participation.pk" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve an Apple Pass template ## Endpoint ``` GET /v2/credentials/compact-semantic/digital-pass/apple/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/apple/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing Apple Pass template by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_RETRIEVE_START * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_RETRIEVE_FAIL ### Path Parameters - `id`: string (required) Apple Pass template ID ### Responses #### 200 - Apple Pass template retrieved ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "fileName": "certificate_of_participation.pkpass", "teamIdentifier": "GH5P43ABC", "passTypeIdentifier": "pass.myproject.participation.pk" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete an Apple Pass template ## Endpoint ``` DELETE /v2/credentials/compact-semantic/digital-pass/apple/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/apple/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes an existing Apple Pass template by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_DELETE_START * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_DELETE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_DELETE_FAIL ### Path Parameters - `id`: string (required) Apple Pass template ID ### Responses #### 204 - Apple Pass template deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update an Apple Pass template ## Endpoint ``` PUT /v2/credentials/compact-semantic/digital-pass/apple/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/apple/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates an existing Apple Pass template by providing its ID and a `.zip` file. Refer to our [Design an Apple Pass template](https://learn.mattr.global/docs/issuance/cwt-credential-templates/apple-templates) guide for more information on how to design the template and how to structure the `.zip` file. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_UPDATE_START * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_UPDATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_APPLE_PASS_TEMPLATE_UPDATE_FAIL ### Path Parameters - `id`: string (required) Apple Pass template ID ### Request Body ### Responses #### 200 - Apple Pass template updated ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "fileName": "certificate_of_participation.pkpass", "teamIdentifier": "GH5P43ABC", "passTypeIdentifier": "pass.myproject.participation.pk" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Google Pass templates ## Endpoint ``` GET /v2/credentials/compact-semantic/digital-pass/google/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/google/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves a list of all Google Pass templates available on your tenant. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_RETRIEVE_LIST_START * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Google Pass templates retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1h", "data": [ { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "issuerId": "3388000000012346000", "serviceAccountClientEmail": "app-user@myproject.iam.gserviceaccount.com", "payPassId": "3388000000012345678.a0bbe92f-c85e-4081-94c3-f842bcd5e463" } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a Google Pass template ## Endpoint ``` POST /v2/credentials/compact-semantic/digital-pass/google/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/google/templates` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Creates a Google Pass template based on the provided `.zip` file. Refer to our [Design a Google Pass template](https://learn.mattr.global/docs/issuance/cwt-credential-templates/google-templates) guide for more information on how to design the template and how to structure the `.zip` file. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_CREATE_START * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_CREATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_CREATE_FAIL ### Request Body ### Responses #### 201 - Google Pass template created ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "issuerId": "3388000000012346000", "serviceAccountClientEmail": "app-user@myproject.iam.gserviceaccount.com", "payPassId": "3388000000012345678.a0bbe92f-c85e-4081-94c3-f842bcd5e463" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a Google Pass template ## Endpoint ``` GET /v2/credentials/compact-semantic/digital-pass/google/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/google/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing Google Pass template by providing its ID. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_RETRIEVE_START * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_RETRIEVE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_RETRIEVE_FAIL ### Path Parameters - `id`: string (required) Google Pass template ID ### Responses #### 200 - Google Pass templated retrieved ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "issuerId": "3388000000012346000", "serviceAccountClientEmail": "app-user@myproject.iam.gserviceaccount.com", "payPassId": "3388000000012345678.a0bbe92f-c85e-4081-94c3-f842bcd5e463" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a Google Pass template ## Endpoint ``` DELETE /v2/credentials/compact-semantic/digital-pass/google/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/google/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Delete a Google Pay Pass template by ID ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_DELETE_START * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_DELETE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_DELETE_FAIL ### Path Parameters - `id`: string (required) Google Pass template ID ### Responses #### 204 - Google Pass template deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a Google Pass template ## Endpoint ``` PUT /v2/credentials/compact-semantic/digital-pass/google/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/compact-semantic/digital-pass/google/templates/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates a existing Google Pass template by providing its ID and a `.zip` file. Refer to our [Design a Google Pass template](https://learn.mattr.global/docs/issuance/cwt-credential-templates/google-templates) guide for more information on how to design the template and how to structure the `.zip` file. ### **Analytic events** * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_UPDATE_START * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_UPDATE_SUCCESS * CREDENTIAL_COMPACT_SEMANTIC_GOOGLE_PASS_TEMPLATE_UPDATE_FAIL ### Path Parameters - `id`: string (required) Google Pass template ID ### Request Body ### Responses #### 200 - Google Pass template updated ```json { "id": "3812166c-ac9f-4e4e-96dd-c1336b5be378", "name": "Certificate of participation", "metadata": { "issuerId": "3388000000012346000", "serviceAccountClientEmail": "app-user@myproject.iam.gserviceaccount.com", "payPassId": "3388000000012345678.a0bbe92f-c85e-4081-94c3-f842bcd5e463" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all IACAs ## Endpoint ``` GET /v2/credentials/mobile/iacas ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/iacas` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves all existing IACAs from the tenant. ### **Analytic events** * MOBILE_CREDENTIAL_IACA_RETRIEVE_LIST_START * MOBILE_CREDENTIAL_IACA_RETRIEVE_LIST_SUCCESS * MOBILE_CREDENTIAL_IACA_RETRIEVE_LIST_FAIL ### Responses #### 200 - IACAs Retrieved ```json { "data": [ { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0zMzA5\r\nMDgyMzM0MjJaMCIxIDAJBgNVBAYTAk5aMBMGA1UEAxMMRXhhbXBsZSBJQUNBMFkw\r\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBbK7JKKFMWuu8kHQK2qaML+MQ0Ykk3Qg\r\n/p3TC6lQKvYJozPSpLXbJQIzMPq9u/dG+j4vq1iX/G/jFIwfiEiKEqOB0TCBzjAS\r\nBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIABjAdBgNVHQ4EFgQU9zTh\r\nKsqFxAgRJDDGW1au+ewJK6owHgYDVR0SBBcwFYYTaHR0cHM6Ly9leGFtcGxlLmNv\r\nbTBpBgNVHR8EYjBgMF6gXKBahlhodHRwczovL2V4YW1wbGUuY29tL3YyL2NyZWRl\r\nbnRpYWxzL21vYmlsZS9pYWNhcy8yZTg5YzE1Ni0zMWQ1LTQ3ODMtYmQ1OS05MDU1\r\nYjVmOGU3ZDIvY3JsMAoGCCqGSM49BAMCA0gAMEUCIQDD+eU8iOsYYC0v41L94fhF\r\nZ0brPo4gx2aRxrhE3NLFpwIgIgHCPBXJ+JICJg3K7dEsr153So4SEZzAA1rRn4eF\r\nvkM=\r\n-----END CERTIFICATE-----\r\n", "certificateData": { "notAfter": "2034-09-26", "notBefore": "2023-09-26", "country": "US", "commonName": "{tenant-subdomain}.vii.mattr.global IACA", "stateOrProvinceName": "US-AL" }, "certificateFingerprint": "3c06145a53e6c252091a71540f870d4d521dede9f176a681a74e38ddc47bb311", "isManaged": true } ] } ``` # Create an IACA ## Endpoint ``` POST /v2/credentials/mobile/iacas ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/iacas` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Creates a new IACA that can be used to sign certificates for Document and Status List signers. - IACAs are always created as inactive. You must manually [update](#operation/update-mobile-credential-iaca) the IACA to [`active: true`](#operation/update-mobile-credential-iaca!path=active&t=request) before it can be used to sign mDocs. - A maximum of three IACAs can be created per tenant. ### **Analytic events** * MOBILE_CREDENTIAL_IACA_CREATE_START * MOBILE_CREDENTIAL_IACA_CREATE_SUCCESS * MOBILE_CREDENTIAL_IACA_CREATE_FAIL ### Request Body ### Responses #### 201 - IACA created ```json { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0zMzA5\r\nMDgyMzM0MjJaMCIxIDAJBgNVBAYTAk5aMBMGA1UEAxMMRXhhbXBsZSBJQUNBMFkw\r\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBbK7JKKFMWuu8kHQK2qaML+MQ0Ykk3Qg\r\n/p3TC6lQKvYJozPSpLXbJQIzMPq9u/dG+j4vq1iX/G/jFIwfiEiKEqOB0TCBzjAS\r\nBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIABjAdBgNVHQ4EFgQU9zTh\r\nKsqFxAgRJDDGW1au+ewJK6owHgYDVR0SBBcwFYYTaHR0cHM6Ly9leGFtcGxlLmNv\r\nbTBpBgNVHR8EYjBgMF6gXKBahlhodHRwczovL2V4YW1wbGUuY29tL3YyL2NyZWRl\r\nbnRpYWxzL21vYmlsZS9pYWNhcy8yZTg5YzE1Ni0zMWQ1LTQ3ODMtYmQ1OS05MDU1\r\nYjVmOGU3ZDIvY3JsMAoGCCqGSM49BAMCA0gAMEUCIQDD+eU8iOsYYC0v41L94fhF\r\nZ0brPo4gx2aRxrhE3NLFpwIgIgHCPBXJ+JICJg3K7dEsr153So4SEZzAA1rRn4eF\r\nvkM=\r\n-----END CERTIFICATE-----\r\n", "certificateData": { "notAfter": "2034-09-26", "notBefore": "2023-09-26", "country": "US", "commonName": "{tenant-subdomain}.vii.mattr.global IACA", "stateOrProvinceName": "US-AL" }, "certificateFingerprint": "3c06145a53e6c252091a71540f870d4d521dede9f176a681a74e38ddc47bb311", "isManaged": true } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 409 - Maximum number of IACA certificates reached. Please delete an existing certificate before creating a new one. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve an IACA ## Endpoint ``` GET /v2/credentials/mobile/iacas/{iacaId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/iacas/{iacaId}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing IACA by providing its ID. ### **Analytic events** * MOBILE_CREDENTIAL_IACA_RETRIEVE_START * MOBILE_CREDENTIAL_IACA_RETRIEVE_SUCCESS * MOBILE_CREDENTIAL_IACA_RETRIEVE_FAIL ### Path Parameters - `iacaId`: string (required) IACA ID ### Responses #### 200 - IACA retrieved ```json { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0zMzA5\r\nMDgyMzM0MjJaMCIxIDAJBgNVBAYTAk5aMBMGA1UEAxMMRXhhbXBsZSBJQUNBMFkw\r\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBbK7JKKFMWuu8kHQK2qaML+MQ0Ykk3Qg\r\n/p3TC6lQKvYJozPSpLXbJQIzMPq9u/dG+j4vq1iX/G/jFIwfiEiKEqOB0TCBzjAS\r\nBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIABjAdBgNVHQ4EFgQU9zTh\r\nKsqFxAgRJDDGW1au+ewJK6owHgYDVR0SBBcwFYYTaHR0cHM6Ly9leGFtcGxlLmNv\r\nbTBpBgNVHR8EYjBgMF6gXKBahlhodHRwczovL2V4YW1wbGUuY29tL3YyL2NyZWRl\r\nbnRpYWxzL21vYmlsZS9pYWNhcy8yZTg5YzE1Ni0zMWQ1LTQ3ODMtYmQ1OS05MDU1\r\nYjVmOGU3ZDIvY3JsMAoGCCqGSM49BAMCA0gAMEUCIQDD+eU8iOsYYC0v41L94fhF\r\nZ0brPo4gx2aRxrhE3NLFpwIgIgHCPBXJ+JICJg3K7dEsr153So4SEZzAA1rRn4eF\r\nvkM=\r\n-----END CERTIFICATE-----\r\n", "certificateData": { "notAfter": "2034-09-26", "notBefore": "2023-09-26", "country": "US", "commonName": "{tenant-subdomain}.vii.mattr.global IACA", "stateOrProvinceName": "US-AL" }, "certificateFingerprint": "3c06145a53e6c252091a71540f870d4d521dede9f176a681a74e38ddc47bb311", "isManaged": true } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete an IACA ## Endpoint ``` DELETE /v2/credentials/mobile/iacas/{iacaId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/iacas/{iacaId}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Deletes an existing IACA by providing its ID. ### **Analytic events** * MOBILE_CREDENTIAL_IACA_DELETE_START * MOBILE_CREDENTIAL_IACA_DELETE_LIST_SUCCESS * MOBILE_CREDENTIAL_IACA_DELETE_LIST_FAIL ### Path Parameters - `iacaId`: string (required) IACA ID ### Responses #### 204 - IACA deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update an IACA ## Endpoint ``` PUT /v2/credentials/mobile/iacas/{iacaId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/iacas/{iacaId}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Update the status of an IACA. Only active IACAs can be used for signing mDocs. Creating an IACA with `active` set to `false` enables distributing the IACA's PEM to relying parties in advance, before it is being used to sign any mDocs. ### **Analytic events** * MOBILE_CREDENTIAL_IACA_UPDATE_START * MOBILE_CREDENTIAL_IACA_UPDATE_SUCCESS * MOBILE_CREDENTIAL_IACA_UPDATE_FAIL ### Path Parameters - `iacaId`: string (required) IACA ID ### Request Body ```json { "active": false } ``` ### Responses #### 200 - IACA updated ```json { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICDjCCAbSgAwIBAgIKdeZsA5NPKimuAzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\r\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0zMzA5\r\nMDgyMzM0MjJaMCIxIDAJBgNVBAYTAk5aMBMGA1UEAxMMRXhhbXBsZSBJQUNBMFkw\r\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBbK7JKKFMWuu8kHQK2qaML+MQ0Ykk3Qg\r\n/p3TC6lQKvYJozPSpLXbJQIzMPq9u/dG+j4vq1iX/G/jFIwfiEiKEqOB0TCBzjAS\r\nBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIABjAdBgNVHQ4EFgQU9zTh\r\nKsqFxAgRJDDGW1au+ewJK6owHgYDVR0SBBcwFYYTaHR0cHM6Ly9leGFtcGxlLmNv\r\nbTBpBgNVHR8EYjBgMF6gXKBahlhodHRwczovL2V4YW1wbGUuY29tL3YyL2NyZWRl\r\nbnRpYWxzL21vYmlsZS9pYWNhcy8yZTg5YzE1Ni0zMWQ1LTQ3ODMtYmQ1OS05MDU1\r\nYjVmOGU3ZDIvY3JsMAoGCCqGSM49BAMCA0gAMEUCIQDD+eU8iOsYYC0v41L94fhF\r\nZ0brPo4gx2aRxrhE3NLFpwIgIgHCPBXJ+JICJg3K7dEsr153So4SEZzAA1rRn4eF\r\nvkM=\r\n-----END CERTIFICATE-----\r\n", "certificateData": { "notAfter": "2034-09-26", "notBefore": "2023-09-26", "country": "US", "commonName": "{tenant-subdomain}.vii.mattr.global IACA", "stateOrProvinceName": "US-AL" }, "certificateFingerprint": "3c06145a53e6c252091a71540f870d4d521dede9f176a681a74e38ddc47bb311", "isManaged": true } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve IACA CRL ## Endpoint ``` GET /v2/credentials/mobile/iacas/{iacaId}/crl ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/iacas/{iacaId}/crl` ### Authorization None required. ## Description Retrieves the Certificate Revocation List (CRL) for the specified IACA in DER binary format. This endpoint is public and does not require authentication. CRLs must be publicly accessible so relying parties can validate certificates. ### **Analytic events** * MOBILE_CREDENTIAL_IACA_CRL_RETRIEVE_START * MOBILE_CREDENTIAL_IACA_CRL_RETRIEVE_SUCCESS * MOBILE_CREDENTIAL_IACA_CRL_RETRIEVE_FAIL ### Path Parameters - `iacaId`: string (required) IACA identifier ### Responses #### 200 - IACA CRL retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Document Signers ## Endpoint ``` GET /v2/credentials/mobile/document-signers ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/document-signers` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves all existing Document Signers from the tenant. ### **Analytic events** * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_RETRIEVE_LIST_START * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_RETRIEVE_LIST_SUCCESS * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_RETRIEVE_LIST_FAIL ### Responses #### 200 - Document Signers retrieved # Create a Document Signer ## Endpoint ``` POST /v2/credentials/mobile/document-signers ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/document-signers` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Creates a new Document Signer that can be used to sign new mDocs. - Only available in implementations using unmanaged (external) IACAs. - A maximum of five Document Signers can be created per tenant. ### **Analytic events** * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_CREATE_START * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_CREATE_SUCCESS * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_CREATE_FAIL ### Request Body ### Responses #### 201 - Document Signer created ```json { "csrPem": "-----BEGIN CERTIFICATE REQUEST-----...-----END CERTIFICATE REQUEST-----" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 409 - Maximum number of document signer certificates reached. Please delete an existing certificate before creating a new one. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a Document Signer ## Endpoint ``` GET /v2/credentials/mobile/document-signers/{documentSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/document-signers/{documentSignerId}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing Document Signer by providing its ID. ### **Analytic events** * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_RETRIEVE_START * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_RETRIEVE_SUCCESS * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_RETRIEVE_FAIL ### Path Parameters - `documentSignerId`: string (required) Document Signer ID ### Responses #### 200 - Document Signer retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a Document Signer ## Endpoint ``` DELETE /v2/credentials/mobile/document-signers/{documentSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/document-signers/{documentSignerId}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Deletes an existing Document Signer by providing its ID. Only available in implementations using unmanaged (external) IACAs. ### **Analytic events** * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_DELETE_START * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_DELETE_SUCCESS * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_DELETE_FAIL ### Path Parameters - `documentSignerId`: string (required) Document Signer ID ### Responses #### 204 - Document Signer deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a Document Signer ## Endpoint ``` PUT /v2/credentials/mobile/document-signers/{documentSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/document-signers/{documentSignerId}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Updates an existing Document Signer by providing its ID and `active` parameter. Only available in implementations using unmanaged (external) IACAs. ### **Analytic events** * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_UPDATE_START * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_UPDATE_SUCCESS * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_UPDATE_FAIL ### Path Parameters - `documentSignerId`: string (required) Document Signer ID ### Request Body ```json { "certificatePem": "-----BEGIN CERTIFICATE-----\\r\\nMIICbzCCAhSgAwIBAgIKfS7sskyJEh+DOzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\\r\\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0yNDA5\\r\\nMTAyMzM0MjJaMDExLzAJBgNVBAYTAk5aMCIGA1UEAxMbZXhhbXBsZS5jb20gRG9j\\r\\ndW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7fa+jv9zCtHQ\\r\\nmKn7o1dS6lBHD5thlhPqjlx7qEfqy8Im9AcQJDal2sr/fUxhHwf/G4ublS7AL04U\\r\\n73dzr/ozxaOCASEwggEdMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLdNNPTmPxt0\\r\\nLqvlZnV/QL86MXOxMB8GA1UdIwQYMBaAFPc04SrKhcQIESQwxltWrvnsCSuqMA4G\\r\\nA1UdDwEB/wQEAwIAgDAeBgNVHREEFzAVhhNodHRwczovL2V4YW1wbGUuY29tMB4G\\r\\nA1UdEgQXMBWGE2h0dHBzOi8vZXhhbXBsZS5jb20waQYDVR0fBGIwYDBeoFygWoZY\\r\\naHR0cHM6Ly9leGFtcGxlLmNvbS92Mi9jcmVkZW50aWFscy9tb2JpbGUvaWFjYXMv\\r\\nMmU4OWMxNTYtMzFkNS00NzgzLWJkNTktOTA1NWI1ZjhlN2QyL2NybDASBgNVHSUE\\r\\nCzAJBgcogYxdBQECMAoGCCqGSM49BAMCA0kAMEYCIQCfgn6+QoNfDVelJANl+Jp9\\r\\ncq7X9paZylfnI6UGr1FM6gIhAIzhiyclDa8+/ZSRfu7KfgGrNRaJ8YQ6vevskJls\\r\\nIavC\\r\\n-----END CERTIFICATE-----\\r\\n" } ``` ### Responses #### 200 - Document Signer updated ```json { "certificatePem": "-----BEGIN CERTIFICATE-----\\r\\nMIICbzCCAhSgAwIBAgIKfS7sskyJEh+DOzAKBggqhkjOPQQDAjAiMSAwCQYDVQQG\\r\\nEwJOWjATBgNVBAMTDEV4YW1wbGUgSUFDQTAeFw0yMzA5MTEyMzM0MjJaFw0yNDA5\\r\\nMTAyMzM0MjJaMDExLzAJBgNVBAYTAk5aMCIGA1UEAxMbZXhhbXBsZS5jb20gRG9j\\r\\ndW1lbnQgU2lnbmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7fa+jv9zCtHQ\\r\\nmKn7o1dS6lBHD5thlhPqjlx7qEfqy8Im9AcQJDal2sr/fUxhHwf/G4ublS7AL04U\\r\\n73dzr/ozxaOCASEwggEdMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFLdNNPTmPxt0\\r\\nLqvlZnV/QL86MXOxMB8GA1UdIwQYMBaAFPc04SrKhcQIESQwxltWrvnsCSuqMA4G\\r\\nA1UdDwEB/wQEAwIAgDAeBgNVHREEFzAVhhNodHRwczovL2V4YW1wbGUuY29tMB4G\\r\\nA1UdEgQXMBWGE2h0dHBzOi8vZXhhbXBsZS5jb20waQYDVR0fBGIwYDBeoFygWoZY\\r\\naHR0cHM6Ly9leGFtcGxlLmNvbS92Mi9jcmVkZW50aWFscy9tb2JpbGUvaWFjYXMv\\r\\nMmU4OWMxNTYtMzFkNS00NzgzLWJkNTktOTA1NWI1ZjhlN2QyL2NybDASBgNVHSUE\\r\\nCzAJBgcogYxdBQECMAoGCCqGSM49BAMCA0kAMEYCIQCfgn6+QoNfDVelJANl+Jp9\\r\\ncq7X9paZylfnI6UGr1FM6gIhAIzhiyclDa8+/ZSRfu7KfgGrNRaJ8YQ6vevskJls\\r\\nIavC\\r\\n-----END CERTIFICATE-----\\r\\n", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "notAfter": "2034-09-26", "notBefore": "2023-09-30", "country": "US", "stateOrProvinceName": "US-AL", "commonName": "{tenant-subdomain}.vii.mattr.global Document Signer" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Revoke a Document Signer ## Endpoint ``` POST /v2/credentials/mobile/document-signers/{documentSignerId}/revoke ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/document-signers/{documentSignerId}/revoke` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Revokes an existing Document Signer, making it unusable for signing new mDocs. If the verifier checks the CRL referenced in the IACA certificate, it must treat revoked Document Signers and any mDocs they signed as untrusted. Only available in implementations using managed IACAs. When using unmanaged (external) IACAs, you must revoke the Document Signer certificate directly with the CA that issued it. ### **Analytic events** * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_REVOKE_START * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_REVOKE_SUCCESS * MOBILE_CREDENTIAL_DOCUMENT_SIGNER_REVOKE_FAIL ### Path Parameters - `documentSignerId`: string (required) Document Signer identifier ### Request Body ### Responses #### 200 - Document Signer revoked ```json { "revoked": true, "revocationDate": "2025-10-31T23:59:59Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 409 - Document Signer already revoked ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all status list signers ## Endpoint ``` GET /v2/credentials/mobile/status-list-signers ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-list-signers` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves all existing status list signers. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_RETRIEVE_LIST_START * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_RETRIEVE_LIST_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_RETRIEVE_LIST_FAIL ### Responses #### 200 - Status list signers retrieved # Create a new status list signer ## Endpoint ``` POST /v2/credentials/mobile/status-list-signers ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-list-signers` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Uses an existing IACA to sign a status list signer (intermediate certificate) that can be used to sign status list tokens. - Only available in implementations using unmanaged (external) IACAs. - A maximum of three Status List Signers can be created per tenant. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_CREATE_START * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_CREATE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_CREATE_FAIL ### Request Body ### Responses #### 200 - Status list signer created #### 409 - Maximum number of status list signer certificates reached. Please delete an existing certificate before creating a new one. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a status list signer ## Endpoint ``` GET /v2/credentials/mobile/status-list-signers/{statusListSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-list-signers/{statusListSignerId}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing status list signer. Status list signer operations are only available in implementations using unmanaged (external) IACAs. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_RETRIEVE_START * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_RETRIEVE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_RETRIEVE_FAIL ### Path Parameters - `statusListSignerId`: string (required) Status list identifier ### Responses #### 200 - Status list signer retrieved # Delete a status list signer ## Endpoint ``` DELETE /v2/credentials/mobile/status-list-signers/{statusListSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-list-signers/{statusListSignerId}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Deletes an existing status list signer. Only available in implementations using unmanaged (external) IACAs. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_DELETE_START * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_DELETE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_DELETE_FAIL ### Path Parameters - `statusListSignerId`: string (required) Status list identifier ### Responses #### 204 - No Content # Update a status list signer ## Endpoint ``` PUT /v2/credentials/mobile/status-list-signers/{statusListSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-list-signers/{statusListSignerId}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Updates the status of an existing status list signer. Only available in implementations using unmanaged (external) IACAs. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_UPDATE_START * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_UPDATE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_UPDATE_FAIL ### Path Parameters - `statusListSignerId`: string (required) Status list identifier ### Request Body ### Responses #### 200 - Status list signer updated ```json { "active": true, "certificateFingerprint": "475DA948E4BA44D9B5BC31AB4B8006113FD5F538" } ``` # Revoke a status list signer ## Endpoint ``` POST /v2/credentials/mobile/status-list-signers/{statusListSignerId}/revoke ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-list-signers/{statusListSignerId}/revoke` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Revokes an existing status list signer, making it unusable for signing new status lists. If the verifier checks the CRL referenced in the IACA certificate, it must treat revoked status list signers and any status list they signed as untrusted. Only available in implementations using managed IACAs. When using unmanaged (external) IACAs, you must revoke the status list signer certificate directly with the CA that issued it. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_REVOKE_START * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_REVOKE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_SIGNER_REVOKE_FAIL ### Path Parameters - `statusListSignerId`: string (required) Status list signer identifier ### Request Body ### Responses #### 200 - Status list signer revoked ```json { "revoked": true, "revocationDate": "2025-10-31T23:59:59Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 409 - Status list signer already revoked ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete mDoc metadata ## Endpoint ``` DELETE /v2/credentials/mobile/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes all stored data for an existing mDoc that matches the provided ID. Removed credential data cannot be recovered. ### **Analytic events** * USER_CREDENTIAL_DELETE_START * USER_CREDENTIAL_DELETE_SUCCESS * USER_CREDENTIAL_DELETE_FAIL ### Responses #### 204 - mDoc metadata deleted #### 400 - Invalid id parameter format ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all mDocs configurations ## Endpoint ``` GET /v2/credentials/mobile/configurations ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/configurations` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves all mDocs configurations from your tenant. ### **Analytic events** * MOBILE_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_START * MOBILE_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_SUCCESS * MOBILE_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. - `type`: string Optional credential type to filter on ### Responses #### 200 - mDocs configurations retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "983c0a86-204f-4431-9371-f5a22e506599", "branding": { "name": "Credential name", "description": "Credential Description", "backgroundColor": "#FFFFFF", "watermarkImage": "data:image/png;base64,{image-data}", "issuerLogo": "data:image/png;base64,{image-data}", "issuerIcon": "data:image/svg+xml;base64,{image-data}" }, "includeStatus": true, "type": "org.iso.18013.5.1.mDL", "claimMappings": { "org.iso.18013.5.1": { "given_name": { "mapFrom": "claims.given_name", "required": true, "type": "string", "display": [ { "name": "Given Name", "locale": "en-US" }, { "name": "Vorname", "locale": "de-DE" } ] }, "birth_date": { "mapFrom": "claims.date_of_birth", "required": true, "type": "dateTime" } } }, "expiresIn": { "months": 1 } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create an mDocs configuration ## Endpoint ``` POST /v2/credentials/mobile/configurations ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/configurations` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Creates a new mDocs configuration, a specific set of rules and parameters that are used to create and validate a particular type of verifiable credential. These rules and parameters define how the credential is structured and what data it contains when issued. ### **Analytic events** * MOBILE_CREDENTIAL_CONFIGURATION_CREATE_START * MOBILE_CREDENTIAL_CONFIGURATION_CREATE_SUCCESS * MOBILE_CREDENTIAL_CONFIGURATION_CREATE_FAIL ### Request Body The mDocs configuration payload ```json { "type": "org.iso.18013.5.1.mDL", "claimMappings": { "org.iso.18013.5.1": { "given_name": { "mapFrom": "claims.given_name", "required": true, "type": "string", "display": [ { "name": "Given Name", "locale": "en-US" }, { "name": "Vorname", "locale": "de-DE" } ] }, "birth_date": { "mapFrom": "claims.date_of_birth", "required": true, "type": "dateTime" } } }, "expiresIn": { "months": 1 }, "branding": { "name": "Credential name", "description": "Credential Description", "backgroundColor": "#FFFFFF", "watermarkImage": "data:image/png;base64,{image-data}", "issuerLogo": "https://example-path-to-image-data.com", "issuerIcon": "data:image/svg+xml;base64,{image-data}" }, "includeStatus": true } ``` ### Responses #### 201 - mDocs configuration created ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "branding": { "name": "Credential name", "description": "Credential Description", "backgroundColor": "#FFFFFF", "watermarkImage": "data:image/png;base64,{image-data}", "issuerLogo": "data:image/png;base64,{image-data}", "issuerIcon": "data:image/svg+xml;base64,{image-data}" }, "includeStatus": true, "type": "org.iso.18013.5.1.mDL", "claimMappings": { "org.iso.18013.5.1": { "given_name": { "mapFrom": "claims.given_name", "required": true, "type": "string", "display": [ { "name": "Given Name", "locale": "en-US" }, { "name": "Vorname", "locale": "de-DE" } ] }, "birth_date": { "mapFrom": "claims.date_of_birth", "required": true, "type": "dateTime" } } }, "expiresIn": { "months": 1 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve an mDocs configuration ## Endpoint ``` GET /v2/credentials/mobile/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing mDocs configuration by providing its ID. ### **Analytic events** * MOBILE_CREDENTIAL_CONFIGURATION_RETRIEVE_START * MOBILE_CREDENTIAL_CONFIGURATION_RETRIEVE_SUCCESS * MOBILE_CREDENTIAL_CONFIGURATION_RETRIEVE_FAIL ### Responses #### 200 - mDocs configuration retrieved ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "branding": { "name": "Credential name", "description": "Credential Description", "backgroundColor": "#FFFFFF", "watermarkImage": "data:image/png;base64,{image-data}", "issuerLogo": "data:image/png;base64,{image-data}", "issuerIcon": "data:image/svg+xml;base64,{image-data}" }, "includeStatus": true, "type": "org.iso.18013.5.1.mDL", "claimMappings": { "org.iso.18013.5.1": { "given_name": { "mapFrom": "claims.given_name", "required": true, "type": "string", "display": [ { "name": "Given Name", "locale": "en-US" }, { "name": "Vorname", "locale": "de-DE" } ] }, "birth_date": { "mapFrom": "claims.date_of_birth", "required": true, "type": "dateTime" } } }, "expiresIn": { "months": 1 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete an mDocs configuration ## Endpoint ``` DELETE /v2/credentials/mobile/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Deletes an existing mDocs configuration by providing its ID. ### **Analytic events** * MOBILE_CREDENTIAL_CONFIGURATION_DELETE_START * MOBILE_CREDENTIAL_CONFIGURATION_DELETE_SUCCESS * MOBILE_CREDENTIAL_CONFIGURATION_DELETE_FAIL ### Responses #### 204 - mDocs configuration deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update an mDocs configuration ## Endpoint ``` PUT /v2/credentials/mobile/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Updates an existing mDocs configuration by providing its ID. ### **Analytic events** * MOBILE_CREDENTIAL_CONFIGURATION_UPDATE_START * MOBILE_CREDENTIAL_CONFIGURATION_UPDATE_SUCCESS * MOBILE_CREDENTIAL_CONFIGURATION_UPDATE_FAIL ### Request Body Update an mDocs configuration ```json { "type": "org.iso.18013.5.1.mDL", "claimMappings": { "org.iso.18013.5.1": { "given_name": { "mapFrom": "claims.given_name", "required": true, "type": "string", "display": [ { "name": "Given Name", "locale": "en-US" }, { "name": "Vorname", "locale": "de-DE" } ] }, "birth_date": { "mapFrom": "claims.date_of_birth", "required": true, "type": "dateTime" } } }, "expiresIn": { "months": 1 }, "branding": { "name": "Credential name", "description": "Credential Description", "backgroundColor": "#FFFFFF", "watermarkImage": "data:image/png;base64,{image-data}", "issuerLogo": "https://example-path-to-image-data.com", "issuerIcon": "data:image/svg+xml;base64,{image-data}" }, "includeStatus": true } ``` ### Responses #### 200 - mDocs configuration updated ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "branding": { "name": "Credential name", "description": "Credential Description", "backgroundColor": "#FFFFFF", "watermarkImage": "data:image/png;base64,{image-data}", "issuerLogo": "data:image/png;base64,{image-data}", "issuerIcon": "data:image/svg+xml;base64,{image-data}" }, "includeStatus": true, "type": "org.iso.18013.5.1.mDL", "claimMappings": { "org.iso.18013.5.1": { "given_name": { "mapFrom": "claims.given_name", "required": true, "type": "string", "display": [ { "name": "Given Name", "locale": "en-US" }, { "name": "Vorname", "locale": "de-DE" } ] }, "birth_date": { "mapFrom": "claims.date_of_birth", "required": true, "type": "dateTime" } } }, "expiresIn": { "months": 1 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve mDoc status ## Endpoint ``` GET /v2/credentials/mobile/{credentialId}/status ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/{credentialId}/status` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves the status of an existing mDoc by providing its `credentialId`. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_RETRIEVE_START * MOBILE_CREDENTIAL_STATUS_RETRIEVE_SUCCESS * MOBILE_CREDENTIAL_STATUS_RETRIEVE_FAIL ### Path Parameters - `credentialId`: string (required) mDoc identifier ### Responses #### 200 - Credential status retrieved ```json { "status": "valid" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update mDoc status ## Endpoint ``` POST /v2/credentials/mobile/{credentialId}/status ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/{credentialId}/status` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Sets the status of an existing mDoc by providing its `credentialId` and the new status. Available status values depend on the Status List configuration format: **Draft 14 of the IETF Token Status List specification (1-bit encoding)**: * **valid** - Credential is valid * **invalid** - Credential is invalid (cannot be reversed) **Deprecated Legacy format (2-bit encoding)**: * **valid** - Credential is valid * **invalid** - Credential is invalid (cannot be reversed) * **suspended** - Credential is temporarily suspended The **suspended** status is deprecated and only available in legacy format. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_SET_START * MOBILE_CREDENTIAL_STATUS_SET_SUCCESS * MOBILE_CREDENTIAL_STATUS_SET_FAIL ### Request Body Credential status payload ```json { "status": "valid" } ``` ### Responses #### 201 - Credential status updated ```json { "status": "valid" } ``` #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Status list configurations ## Endpoint ``` GET /v2/credentials/mobile/status-lists/configurations ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-lists/configurations` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves all Status list configurations from your tenant. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_RETRIEVE_LIST_START * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_RETRIEVE_LIST_ * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Status list configurations retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "983c0a86-204f-4431-9371-f5a22e506599", "docType": "Drivers License", "timeToLiveDuration": { "days": 1 }, "expiryDuration": { "days": 2 } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a Status list configuration ## Endpoint ``` POST /v2/credentials/mobile/status-lists/configurations ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-lists/configurations` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Creates a Status list configuration, which defines a status list validity periods. mDocs can then be assigned to a specific Status list configuration. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_CREATE_START * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_CREATE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_CREATE_FAIL ### Request Body The Status list configuration payload ```json { "docType": "DriverLicense", "timeToLiveDuration": { "days": 1 }, "expiryDuration": { "days": 2 } } ``` ### Responses #### 201 - Status list configuration created ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "docType": "Drivers License", "timeToLiveDuration": { "days": 1 }, "expiryDuration": { "days": 2 } } ``` #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a Status list configuration ## Endpoint ``` GET /v2/credentials/mobile/status-lists/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-lists/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing Status list configuration by providing its ID. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_RETRIEVE_START * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_RETRIEVE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_RETRIEVE_FAIL ### Path Parameters - `id`: string (required) Status list configuration ID ### Responses #### 200 - Status list configuration retrieved ```json { "docType": "DriverLicense", "timeToLiveDuration": { "days": 1 }, "expiryDuration": { "days": 2 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a Status list configuration ## Endpoint ``` DELETE /v2/credentials/mobile/status-lists/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-lists/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Permanently deletes an existing Status list configuration. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_DELETE_START * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_DELETE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_DELETE_FAIL ### Path Parameters - `id`: string (required) Status list configuration ID ### Responses #### 204 - Status list configuration deleted #### 400 - Bad request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a Status list configuration ## Endpoint ``` PUT /v2/credentials/mobile/status-lists/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-lists/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates an existing Status list configuration, allowing you to adjust the expiry and TTL (Time To Live) settings. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_UPDATE_START * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_UPDATE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_CONFIGURATION_UPDATE_FAIL ### Path Parameters - `id`: string (required) Status list configuration ID ### Request Body The Status list configuration payload ```json { "timeToLiveDuration": { "days": 1 }, "expiryDuration": { "days": 2 } } ``` ### Responses #### 200 - Status list configuration updated ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "docType": "Drivers License", "timeToLiveDuration": { "days": 1 }, "expiryDuration": { "days": 2 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Status lists ## Endpoint ``` GET /v2/credentials/mobile/status-lists ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-lists` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description **Deprecated.** In line with our SLA, this endpoint will be removed no earlier than 22 September 2026. To discover the Status lists available on a tenant, use the public [Status list distribution](#operation/getStatusListDistribution) endpoint (`GET /v2/credentials/mobile/status-lists/distribution`), which returns the URLs of all Status list tokens on the tenant. To retrieve an individual Status list token, use the public [Retrieve a Status list token](#operation/getStatusListToken) endpoint (`GET /v2/credentials/mobile/status-lists/{statusListId}/token`). Retrieves all existing status lists from your tenant. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_RETRIEVE_LIST_START * MOBILE_CREDENTIAL_STATUS_LIST_RETRIEVE_LIST_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_RETRIEVE_LIST_FAIL ### Responses #### 200 - Status lists retrieved ```json { "data": [ { "listSize": "100_000", "list": "0oRZAu6jEHRtYXR0ci1zdGF0dXNs..." } ] } ``` # Retrieve a Status list ## Endpoint ``` GET /v2/credentials/mobile/status-lists/{statusListId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-lists/{statusListId}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description **Deprecated.** In line with our SLA, this endpoint will be removed no earlier than 22 September 2026. To retrieve a Status list token, use the public [Retrieve a Status list token](#operation/getStatusListToken) endpoint (`GET /v2/credentials/mobile/status-lists/{statusListId}/token`), which returns the signed Status list token in CWT format. To discover the Status lists available on a tenant, use the public [Status list distribution](#operation/getStatusListDistribution) endpoint (`GET /v2/credentials/mobile/status-lists/distribution`). Retrieves an existing Status list and its signed token by providing its ID. ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_RETRIEVE_START * MOBILE_CREDENTIAL_STATUS_LIST_RETRIEVE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_RETRIEVE_FAIL ### Path Parameters - `statusListId`: string (required) Status list unique identifier ### Responses #### 200 - Status list retrieved ```json { "listSize": "100_000", "list": "0oRZAu6jEHRtYXR0ci1zdGF0dXNs..." } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a Status list token ## Endpoint ``` GET /v2/credentials/mobile/status-lists/{statusListId}/token ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-lists/{statusListId}/token` ### Authorization None required. ## Description Retrieves the Status list token in CWT format. This public endpoint returns a token which contains a compressed, signed list of credential statuses. Relying parties can use this token to check the revocation status of an mDoc that references this Status list. This endpoint is intended for public consumption, and as such does not require authentication. **Token Format Differences**: The token structure depends on the Status List configuration format: **Draft 14 of the IETF Token Status List specification**: - Token header `typ`: `application/statuslist+cwt` - CBOR payload claims: `65533` (status_list), `65534` (ttl) - Status encoding: 1-bit (Valid/Invalid) **Legacy format**: - Token header `typ`: `mattr-statuslist+cwt` - CBOR payload claims: `-65538` (status_list), `-65539` (ttl) - Status encoding: 2-bit (Valid/Invalid/Suspended) ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_TOKEN_RETRIEVE_START * MOBILE_CREDENTIAL_STATUS_LIST_TOKEN_RETRIEVE_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_TOKEN_RETRIEVE_FAIL ### Path Parameters - `statusListId`: string (required) Status list identifier ### Responses #### 200 - Status list token retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 410 - Status list expired ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Status list distribution ## Endpoint ``` GET /v2/credentials/mobile/status-lists/distribution ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/status-lists/distribution` ### Authorization None required. ## Description Retrieves an object that details all existing Status lists tokens on the tenant. This public endpoint allows a relying party to consume and cache status lists. Each list in the response includes a URL where its token can be retrieved. Status list tokens that were signed by expired IACAs are excluded from the response. This endpoint is intended for public consumption, and as such does not require authentication. **Response Format Differences**: The response structure depends on the Status List configuration format: **Draft 14 of the IETF Token Status List specification**: `{"status_lists": ["https://..."]}` **Legacy format**: `{"status_lists": [{"uri": "https://..."}]}` ### **Analytic events** * MOBILE_CREDENTIAL_STATUS_LIST_DISTRIBUTION_START * MOBILE_CREDENTIAL_STATUS_LIST_DISTRIBUTION_SUCCESS * MOBILE_CREDENTIAL_STATUS_LIST_DISTRIBUTION_FAIL ### Responses #### 200 - Status lists retrieved # Retrieve all trusted issuers ## Endpoint ``` GET /v2/credentials/mobile/trusted-issuers ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/trusted-issuers` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves all mDocs trusted issuers from your tenant. ### **Analytic events** * MOBILE_CREDENTIAL_TRUSTED_ISSUER_RETRIEVE_LIST_START * MOBILE_CREDENTIAL_TRUSTED_ISSUER_RETRIEVE_LIST_SUCCESS * MOBILE_CREDENTIAL_TRUSTED_ISSUER_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Trusted issuers retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "ed74319e-72a6-4401-b3a5-94e980fbebea", "certificatePem": "-----BEGIN CERTIFICATE-----\\r\\nMIICUDCCAfWgAwIBAgIKVVqBlVonWFs3lTAKBggqhkjOPQQDAjAkMQswCQYDVQQG\\r\\nEwJOWjEVMBMGA1UEAwwMRXhhbXBsZSBJQUNBMB4XDTI0MDExMTAzMjYwMFoXDTM0\\r\\nMDEwODAzMjYwMFowJDELMAkGA1UEBhMCTloxFTATBgNVBAMMDEV4YW1wbGUgSUFD\\r\\nQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOHxm9MYkCvIvZc/MyoWGul8+tla\\r\\nFSSRVkDllFERbO/Tg7DOj4CJfYrhDJEuV04eRgcowBDhr9W/bvnTMZMa/RijggEN\\r\\nMIIBCTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E\\r\\nFgQUpS3hOCbmCUwu8n91X9CLS682cOkwOwYDVR0SBDQwMoYwaHR0cHM6Ly9odWRz\\r\\nb24tdGVuYW50LTAwMS52aWkuYXUzMDEubWF0dHJsYWJzLmlvMIGGBgNVHR8EfzB9\\r\\nMHugeaB3hnVodHRwczovL2h1ZHNvbi10ZW5hbnQtMDAxLnZpaS5hdTMwMS5tYXR0\\r\\ncmxhYnMuaW8vdjIvY3JlZGVudGlhbHMvbW9iaWxlL2lhY2FzL2VkNzQzMTllLTcy\\r\\nYTYtNDQwMS1iM2E1LTk0ZTk4MGZiZWJlYS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIh\\r\\nAJxWGZvntq+hymL7zWwrlZo1Jz1+lWglu/MESdmUhTNFAiEAg+x5e3TzBxgHneIM\\r\\nVpTmZNOyZI3Hn17WRKkyKSg+5/8=\\r\\n-----END CERTIFICATE-----\\r\\n", "certificateData": { "notAfter": "2033-09-23", "notBefore": "2023-09-23", "country": "NZ", "commonName": "Example Trusted Issuer" } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a trusted issuer ## Endpoint ``` POST /v2/credentials/mobile/trusted-issuers ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/trusted-issuers` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Add a new mDocs trusted issuer, to be used in online presentation workflows. ### **Analytic events** * MOBILE_CREDENTIAL_TRUSTED_ISSUER_CREATE_START * MOBILE_CREDENTIAL_TRUSTED_ISSUER_CREATE_SUCCESS * MOBILE_CREDENTIAL_TRUSTED_ISSUER_CREATE_FAIL ### Request Body The trusted issuer payload ```json { "certificatePem": "-----BEGIN CERTIFICATE-----\\r\\nMIICUDCCAfWgAwIBAgIKVVqBlVonWFs3lTAKBggqhkjOPQQDAjAkMQswCQYDVQQG\\r\\nEwJOWjEVMBMGA1UEAwwMRXhhbXBsZSBJQUNBMB4XDTI0MDExMTAzMjYwMFoXDTM0\\r\\nMDEwODAzMjYwMFowJDELMAkGA1UEBhMCTloxFTATBgNVBAMMDEV4YW1wbGUgSUFD\\r\\nQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOHxm9MYkCvIvZc/MyoWGul8+tla\\r\\nFSSRVkDllFERbO/Tg7DOj4CJfYrhDJEuV04eRgcowBDhr9W/bvnTMZMa/RijggEN\\r\\nMIIBCTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E\\r\\nFgQUpS3hOCbmCUwu8n91X9CLS682cOkwOwYDVR0SBDQwMoYwaHR0cHM6Ly9odWRz\\r\\nb24tdGVuYW50LTAwMS52aWkuYXUzMDEubWF0dHJsYWJzLmlvMIGGBgNVHR8EfzB9\\r\\nMHugeaB3hnVodHRwczovL2h1ZHNvbi10ZW5hbnQtMDAxLnZpaS5hdTMwMS5tYXR0\\r\\ncmxhYnMuaW8vdjIvY3JlZGVudGlhbHMvbW9iaWxlL2lhY2FzL2VkNzQzMTllLTcy\\r\\nYTYtNDQwMS1iM2E1LTk0ZTk4MGZiZWJlYS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIh\\r\\nAJxWGZvntq+hymL7zWwrlZo1Jz1+lWglu/MESdmUhTNFAiEAg+x5e3TzBxgHneIM\\r\\nVpTmZNOyZI3Hn17WRKkyKSg+5/8=\\r\\n-----END CERTIFICATE-----\\r\\n" } ``` ### Responses #### 201 - Trusted issuer created ```json { "id": "ed74319e-72a6-4401-b3a5-94e980fbebea", "certificatePem": "-----BEGIN CERTIFICATE-----\\r\\nMIICUDCCAfWgAwIBAgIKVVqBlVonWFs3lTAKBggqhkjOPQQDAjAkMQswCQYDVQQG\\r\\nEwJOWjEVMBMGA1UEAwwMRXhhbXBsZSBJQUNBMB4XDTI0MDExMTAzMjYwMFoXDTM0\\r\\nMDEwODAzMjYwMFowJDELMAkGA1UEBhMCTloxFTATBgNVBAMMDEV4YW1wbGUgSUFD\\r\\nQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOHxm9MYkCvIvZc/MyoWGul8+tla\\r\\nFSSRVkDllFERbO/Tg7DOj4CJfYrhDJEuV04eRgcowBDhr9W/bvnTMZMa/RijggEN\\r\\nMIIBCTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E\\r\\nFgQUpS3hOCbmCUwu8n91X9CLS682cOkwOwYDVR0SBDQwMoYwaHR0cHM6Ly9odWRz\\r\\nb24tdGVuYW50LTAwMS52aWkuYXUzMDEubWF0dHJsYWJzLmlvMIGGBgNVHR8EfzB9\\r\\nMHugeaB3hnVodHRwczovL2h1ZHNvbi10ZW5hbnQtMDAxLnZpaS5hdTMwMS5tYXR0\\r\\ncmxhYnMuaW8vdjIvY3JlZGVudGlhbHMvbW9iaWxlL2lhY2FzL2VkNzQzMTllLTcy\\r\\nYTYtNDQwMS1iM2E1LTk0ZTk4MGZiZWJlYS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIh\\r\\nAJxWGZvntq+hymL7zWwrlZo1Jz1+lWglu/MESdmUhTNFAiEAg+x5e3TzBxgHneIM\\r\\nVpTmZNOyZI3Hn17WRKkyKSg+5/8=\\r\\n-----END CERTIFICATE-----\\r\\n", "certificateData": { "notAfter": "2033-09-23", "notBefore": "2023-09-23", "country": "NZ", "commonName": "Example Trusted Issuer" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a trusted issuer ## Endpoint ``` GET /v2/credentials/mobile/trusted-issuers/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/trusted-issuers/{id}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves an existing trusted issuer from your tenant by providing its ID. ### **Analytic events** * MOBILE_CREDENTIAL_TRUSTED_ISSUER_RETRIEVE_START * MOBILE_CREDENTIAL_TRUSTED_ISSUER_RETRIEVE_SUCCESS * MOBILE_CREDENTIAL_TRUSTED_ISSUER_RETRIEVE_FAIL ### Responses #### 200 - Trusted issuer retrieved ```json { "id": "ed74319e-72a6-4401-b3a5-94e980fbebea", "certificatePem": "-----BEGIN CERTIFICATE-----\\r\\nMIICUDCCAfWgAwIBAgIKVVqBlVonWFs3lTAKBggqhkjOPQQDAjAkMQswCQYDVQQG\\r\\nEwJOWjEVMBMGA1UEAwwMRXhhbXBsZSBJQUNBMB4XDTI0MDExMTAzMjYwMFoXDTM0\\r\\nMDEwODAzMjYwMFowJDELMAkGA1UEBhMCTloxFTATBgNVBAMMDEV4YW1wbGUgSUFD\\r\\nQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOHxm9MYkCvIvZc/MyoWGul8+tla\\r\\nFSSRVkDllFERbO/Tg7DOj4CJfYrhDJEuV04eRgcowBDhr9W/bvnTMZMa/RijggEN\\r\\nMIIBCTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E\\r\\nFgQUpS3hOCbmCUwu8n91X9CLS682cOkwOwYDVR0SBDQwMoYwaHR0cHM6Ly9odWRz\\r\\nb24tdGVuYW50LTAwMS52aWkuYXUzMDEubWF0dHJsYWJzLmlvMIGGBgNVHR8EfzB9\\r\\nMHugeaB3hnVodHRwczovL2h1ZHNvbi10ZW5hbnQtMDAxLnZpaS5hdTMwMS5tYXR0\\r\\ncmxhYnMuaW8vdjIvY3JlZGVudGlhbHMvbW9iaWxlL2lhY2FzL2VkNzQzMTllLTcy\\r\\nYTYtNDQwMS1iM2E1LTk0ZTk4MGZiZWJlYS9jcmwwCgYIKoZIzj0EAwIDSQAwRgIh\\r\\nAJxWGZvntq+hymL7zWwrlZo1Jz1+lWglu/MESdmUhTNFAiEAg+x5e3TzBxgHneIM\\r\\nVpTmZNOyZI3Hn17WRKkyKSg+5/8=\\r\\n-----END CERTIFICATE-----\\r\\n", "certificateData": { "notAfter": "2033-09-23", "notBefore": "2023-09-23", "country": "NZ", "commonName": "Example Trusted Issuer" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a trusted issuer ## Endpoint ``` DELETE /v2/credentials/mobile/trusted-issuers/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/mobile/trusted-issuers/{id}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Deletes an existing trusted issuer by providing its ID. ### **Analytic events** * MOBILE_CREDENTIAL_TRUSTED_ISSUER_DELETE_START * MOBILE_CREDENTIAL_TRUSTED_ISSUER_DELETE_SUCCESS * MOBILE_CREDENTIAL_TRUSTED_ISSUER_DELETE_FAIL ### Responses #### 204 - Trusted issuer deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all trusted issuers ## Endpoint ``` GET /v2/presentations/trusted-issuers ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/trusted-issuers` ### Authorization None required. ## Description Retrieves all configured trusted issuers. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * CREDENTIAL_PRESENTATION_TRUSTED_ISSUERS_RETRIEVE_LIST_START * CREDENTIAL_PRESENTATION_TRUSTED_ISSUERS_RETRIEVE_LIST_SUCCESS * CREDENTIAL_PRESENTATION_TRUSTED_ISSUERS_RETRIEVE_LIST_FAIL ### Responses #### 200 - Trusted issuers retrieved # Retrieve all verifier root CA certificates ## Endpoint ``` GET /v2/presentations/certificates/ca ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/ca` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves all existing verifier root CA certificates. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_RETRIEVE_LIST_START * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_RETRIEVE_LIST_FAIL ### Responses #### 200 - Verifier root CA certificates retrieved ```json { "data": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "a3b2c1d4e5f60718293a4b5c6d7e8f90123456789abcdef0123456789abcdef0", "certificateData": { "commonName": "Example Verifier", "country": "US", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" } } ] } ``` # Create a verifier root CA certificate ## Endpoint ``` POST /v2/presentations/certificates/ca ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/ca` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Creates a verifier root CA certificate to be used as part of mDocs online verification workflows. - A maximum of three Verifier root CA certificates can be created per tenant. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_CREATE_START * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_CREATE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_CREATE_FAIL ### Request Body Verifier root CA certificate payload ### Responses #### 200 - Verifier root CA certificate created ```json { "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "a3b2c1d4e5f60718293a4b5c6d7e8f90123456789abcdef0123456789abcdef0", "certificateData": { "commonName": "Example Verifier", "country": "US", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 409 - Maximum number of verifier root certificates reached. Please delete an existing certificate before creating a new one. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a verifier root CA certificate ## Endpoint ``` GET /v2/presentations/certificates/ca/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/ca/{certificateId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves an existing verifier root CA certificate. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_RETRIEVE_START * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_RETRIEVE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - Verifier root CA certificate retrieved ```json { "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "a3b2c1d4e5f60718293a4b5c6d7e8f90123456789abcdef0123456789abcdef0", "certificateData": { "commonName": "Example Verifier", "country": "US", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" } } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a verifier root CA certificate ## Endpoint ``` DELETE /v2/presentations/certificates/ca/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/ca/{certificateId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Deletes an existing verifier root CA certificate. ### **Analytic events** * PRESENTATION_VERIFIER_CA_CERTIFICATE_DELETE_START * PRESENTATION_VERIFIER_CA_CERTIFICATE_DELETE_SUCCESS * PRESENTATION_VERIFIER_CA_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - Verifier root CA certificate deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a verifier root CA certificate ## Endpoint ``` PUT /v2/presentations/certificates/ca/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/ca/{certificateId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Updates an existing verifier root CA certificate. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_UPDATE_START * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_UPDATE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_CA_CERTIFICATE_UPDATE_FAIL ### Request Body Verifier root CA certificate payload ### Responses #### 200 - Verifier root CA certificate updated ```json { "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "a3b2c1d4e5f60718293a4b5c6d7e8f90123456789abcdef0123456789abcdef0", "certificateData": { "commonName": "Example Verifier", "country": "US", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Verification request signers ## Endpoint ``` GET /v2/presentations/certificates/verifier-signers ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/verifier-signers` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves all Verification request signers. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_RETRIEVE_LIST_START * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Verification request signers retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a Verification request signer ## Endpoint ``` POST /v2/presentations/certificates/verifier-signers ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/verifier-signers` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Creates a Verification request signer. - Only available in implementations using unmanaged (external) Verifier root CA certificates. - A maximum of five Verification request signers can be created per tenant. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_CREATE_START * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_CREATE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_CREATE_FAIL ### Request Body ### Responses #### 201 - Verification request signer created ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "csrPem": "-----BEGIN CERTIFICATE REQUEST-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE REQUEST-----", "caId": "b0aae560-10e7-4247-8e96-7cdd3578a1e2", "active": false } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a Verification request signer ## Endpoint ``` GET /v2/presentations/certificates/verifier-signers/{verifierSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/verifier-signers/{verifierSignerId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves a Verification request signer. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_RETRIEVE_START * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_RETRIEVE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - Verification request signer retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a Verification request signer ## Endpoint ``` DELETE /v2/presentations/certificates/verifier-signers/{verifierSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/verifier-signers/{verifierSignerId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Deletes a Verification request signer. Only available in implementations using unmanaged (external) Verifier root CA certificates. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_DELETE_START * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_DELETE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - Verification request signer deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a Verification request signer ## Endpoint ``` PUT /v2/presentations/certificates/verifier-signers/{verifierSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/verifier-signers/{verifierSignerId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Updates a Verification request signer by: - Providing a Verification Request Signer Certificate (VRSC) in PEM format that matches its Certificate Signing Request (CSR). - Activating or deactivating the VRSC signer. Only VRSC signers with a valid PEM certificate can be activated. - The `certificatePem` field becomes immutable after it's updated for the first time. Only available in implementations using unmanaged (external) Verifier root CA certificates. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_UPDATE_START * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_UPDATE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_SIGNER_CERTIFICATE_UPDATE_FAIL ### Request Body ```json { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----" } ``` ### Responses #### 200 - Verification request signer updated ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all verifier applications ## Endpoint ``` GET /v2/presentations/applications ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/applications` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves all configured mDocs online verifier applications. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_RETRIEVE_LIST_START * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_RETRIEVE_LIST_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Verifier applications retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "name": "Example Verifier Web Application" } ] } ``` # Create verifier application ## Endpoint ``` POST /v2/presentations/applications ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/applications` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Creates an mDocs online verifier application. Once created, application instances can be registered and interact with the MATTR VII platform (for example, to verify credential presentations). ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_CREATE_START * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_CREATE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_CREATE_FAIL ### Request Body Verifier application payload ```json { "name": "Example Verifier Web Application" } ``` ### Responses #### 201 - Verifier application created ```json { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "name": "Example Verifier Web Application" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a verifier application ## Endpoint ``` GET /v2/presentations/applications/{applicationId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/applications/{applicationId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves an existing mDocs online verifier application. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_RETRIEVE_START * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_RETRIEVE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_RETRIEVE_FAIL ### Responses #### 200 - Verifier application retrieved ```json { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "name": "Example Verifier Web Application" } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a verifier application ## Endpoint ``` DELETE /v2/presentations/applications/{applicationId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/applications/{applicationId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Deletes an existing verifier application. Once deleted, any associated application instances will no longer be able to interact with the MATTR VII platform. ### **Analytic events** * PRESENTATION_VERIFIER_APPLICATION_DELETE_START * PRESENTATION_VERIFIER_APPLICATION_DELETE_SUCCESS * PRESENTATION_VERIFIER_APPLICATION_DELETE_FAIL ### Responses #### 204 - Verifier application deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update verifier application ## Endpoint ``` PUT /v2/presentations/applications/{applicationId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/applications/{applicationId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Updates an existing mDocs online verifier application. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_UPDATE_START * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_UPDATE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_UPDATE_FAIL ### Request Body Verifier application payload ```json { "name": "Example Verifier Web Application" } ``` ### Responses #### 200 - Verifier application updated ```json { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "name": "Example Verifier Web Application" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all registered verifier application instances ## Endpoint ``` GET /v2/presentations/applications/{applicationId}/instances ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/applications/{applicationId}/instances` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves all registered instances of a verifier application. This can be used by application owners to view all registered instances for a given verifier application. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_INSTANCE_RETRIEVE_LIST_START * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_INSTANCE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_INSTANCE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Verifier application instances retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "appAttestationType": "key_attestation", "registeredAt": "2023-10-05T14:48:00.000Z", "licenseExpiresAt": "2024-10-05T14:48:00.000Z", "lastAttestedAt": "2023-12-01T10:30:00.000Z", "externalReferenceId": "external-ref-12345", "deviceDetails": { "deviceModel": "iPhone 12", "deviceMake": "Apple", "osVersion": "iOS 14.4" }, "sdkDetails": { "sdkVersion": "1.2.3" } } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a registered verifier application instance ## Endpoint ``` GET /v2/presentations/applications/{applicationId}/instances/{instanceId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/applications/{applicationId}/instances/{instanceId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves a registered instance of a verifier application. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_INSTANCE_RETRIEVE_START * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_INSTANCE_RETRIEVE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_INSTANCE_RETRIEVE_FAIL ### Responses #### 200 - Verifier application instance retrieved ```json { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "appAttestationType": "key_attestation", "registeredAt": "2023-10-05T14:48:00.000Z", "licenseExpiresAt": "2024-10-05T14:48:00.000Z", "lastAttestedAt": "2023-12-01T10:30:00.000Z", "externalReferenceId": "external-ref-12345", "deviceDetails": { "deviceModel": "iPhone 12", "deviceMake": "Apple", "osVersion": "iOS 14.4" }, "sdkDetails": { "sdkVersion": "1.2.3" } } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a registered verifier application instance ## Endpoint ``` DELETE /v2/presentations/applications/{applicationId}/instances/{instanceId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/applications/{applicationId}/instances/{instanceId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Deletes a registered instance of a verifier application. Deleted instances will no longer be able to interact with the platform or receive tokens, and any existing tokens will be revoked. Application owners can use this endpoint to remove individual instances that are no longer needed or were registered by mistake, without affecting the entire verifier application or its other instances. This is useful for cleaning up test instances or managing specific devices, rather than deleting the whole application and all its associated instances. ### **Analytic events** * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_INSTANCE_DELETE_START * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_INSTANCE_DELETE_SUCCESS * CREDENTIAL_PRESENTATION_VERIFIER_APPLICATION_INSTANCE_DELETE_FAIL ### Responses #### 204 - Verifier application instance deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all wallet providers ## Endpoint ``` GET /v2/presentations/wallet-providers ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/wallet-providers` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves all existing wallet providers that can present mDocs for online verification. ### **Analytic events** * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_RETRIEVE_LIST_START * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_RETRIEVE_LIST_SUCCESS * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_RETRIEVE_LIST_FAIL ### Responses #### 200 - Wallet providers retrieved ```json [ { "id": "e63a2e46-5afa-48f9-bcc2-2114cf5f331b", "name": "Example wallet provider", "openid4vpConfiguration": { "authorizationEndpoint": "com-example.wallet://" } } ] ``` # Create wallet provider ## Endpoint ``` POST /v2/presentations/wallet-providers ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/wallet-providers` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Creates a wallet provider that can present mDocs for online verification. ### **Analytic events** * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_CREATE_START * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_CREATE_SUCCESS * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_CREATE_FAIL ### Request Body Wallet provider payload ```json { "name": "Example wallet provider", "openid4vpConfiguration": { "authorizationEndpoint": "com-example.wallet://" } } ``` ### Responses #### 200 - Wallet provider created ```json { "id": "e63a2e46-5afa-48f9-bcc2-2114cf5f331b", "name": "Example wallet provider", "openid4vpConfiguration": { "authorizationEndpoint": "com-example.wallet://" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a wallet provider ## Endpoint ``` GET /v2/presentations/wallet-providers/{walletProviderId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/wallet-providers/{walletProviderId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves an existing wallet provider that can present mDocs for online verification. ### **Analytic events** * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_RETRIEVE_START * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_RETRIEVE_SUCCESS * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_RETRIEVE_FAIL ### Responses #### 200 - Wallet provider retrieved ```json { "id": "e63a2e46-5afa-48f9-bcc2-2114cf5f331b", "name": "Example wallet provider", "openid4vpConfiguration": { "authorizationEndpoint": "com-example.wallet://" } } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a wallet provider ## Endpoint ``` DELETE /v2/presentations/wallet-providers/{walletProviderId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/wallet-providers/{walletProviderId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Deletes an existing wallet provider that can present mDocs for online verification. ### **Analytic events** * PRESENTATION_WALLET_PROVIDER_DELETE_START * PRESENTATION_WALLET_PROVIDER_DELETE_SUCCESS * PRESENTATION_WALLET_PROVIDER_DELETE_FAIL ### Responses #### 204 - Wallet provider deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a wallet provider ## Endpoint ``` PUT /v2/presentations/wallet-providers/{walletProviderId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/wallet-providers/{walletProviderId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Updates an existing wallet provider that can present mDocs for online verification. ### **Analytic events** * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_UPDATE_START * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_UPDATE_SUCCESS * CREDENTIAL_PRESENTATION_WALLET_PROVIDER_UPDATE_FAIL ### Request Body Wallet provider payload ```json { "name": "Example wallet provider", "openid4vpConfiguration": { "authorizationEndpoint": "com-example.wallet://" } } ``` ### Responses #### 200 - Wallet provider updated ```json { "id": "e63a2e46-5afa-48f9-bcc2-2114cf5f331b", "name": "Example wallet provider", "openid4vpConfiguration": { "authorizationEndpoint": "com-example.wallet://" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve presentation session result ## Endpoint ``` GET /v2/presentations/sessions/{sessionId}/result ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/sessions/{sessionId}/result` ### Authorization Bearer token required. ## Description Retrieves the result of an online presentation session by providing the session's ID. When a holder responds to a verification request, the result indicates whether the presentation succeeded and whether the credentials were verified. The response can be a `PresentationSuccessResult` (holder completed the presentation workflow) or a `PresentationFailureResult` (holder was unable to complete the workflow). A successful presentation may include verified credentials, credential errors (requested credentials not provided), claim errors (specific claims that failed verification), or a combination of these outcomes. Your backend should validate that the returned `challenge` matches the challenge generated before starting the session to protect against session replay attacks. For detailed information about result structures, error types, and complete examples, see the [handling verification results guide](https://learn.mattr.global/docs/verification/remote-web-verifiers/guides/handling-verification-results). ### **Analytic events** * CREDENTIAL_PRESENTATION_SESSION_RESULT_RETRIEVE_START * CREDENTIAL_PRESENTATION_SESSION_RESULT_RETRIEVE_SUCCESS * CREDENTIAL_PRESENTATION_SESSION_RESULT_RETRIEVE_FAIL ### Responses #### 200 - Session result retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Apple Identity Access CSRs ## Endpoint ``` GET /v2/presentations/certificates/apple-identity-access-certificates ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/apple-identity-access-certificates` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves all Apple Identity Access CSRs created by the tenant. ### **Analytic events** * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_RETRIEVE_LIST_START * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_RETRIEVE_LIST_FAIL ### Responses #### 200 - Apple Identity Access CSRs retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "fd44e792-45ac-11f0-bef8-bb24f133065e", "teamId": "A2B3C4D5E6", "merchantId": "com.domain.subdomain" } ] } ``` # Create an Apple Identity Access CSR ## Endpoint ``` POST /v2/presentations/certificates/apple-identity-access-certificates ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/apple-identity-access-certificates` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Creates an Apple Identity Access Certificate Signing Request (CSR) that can be uploaded to the Apple Developer Portal. This certificate contains the public key that will be used to decrypt the response from the Apple Wallet. ### **Analytic events** * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_CREATE_START * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_CREATE_SUCCESS * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_CREATE_FAIL ### Request Body Apple Identity Access CSR payload ```json { "teamId": "A2B3C4D5E6", "merchantId": "com.domain.subdomain" } ``` ### Responses #### 201 - Apple Identity Access CSR created ```json { "id": "fd44e792-45ac-11f0-bef8-bb24f133065e", "teamId": "A2B3C4D5E6", "merchantId": "com.domain.subdomain" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve an Apple Identity Access CSR ## Endpoint ``` GET /v2/presentations/certificates/apple-identity-access-certificates/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/apple-identity-access-certificates/{certificateId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieves an existing Apple Identity Access CSR. ### **Analytic events** * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_RETRIEVE_START * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_RETRIEVE_SUCCESS * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - Apple Identity Access Certificate CSR retrieved ```json { "id": "fd44e792-45ac-11f0-bef8-bb24f133065e", "teamId": "A2B3C4D5E6", "merchantId": "com.domain.subdomain" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete an Apple Identity Access CSR ## Endpoint ``` DELETE /v2/presentations/certificates/apple-identity-access-certificates/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v2/presentations/certificates/apple-identity-access-certificates/{certificateId}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Deletes an existing Apple Identity Access CSR. ### **Analytic events** * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_DELETE_START * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_DELETE_SUCCESS * CREDENTIAL_PRESENTATION_APPLE_IDENTITY_ACCESS_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - Apple Identity Access Certificate CSR deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Sign a JSON credential ## Endpoint ``` POST /v2/credentials/web-semantic/sign ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/sign` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a signed JSON credential generated from a provided valid payload. ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_SIGN_START * CREDENTIAL_WEB_SEMANTIC_SIGN_SUCCESS * CREDENTIAL_WEB_SEMANTIC_SIGN_FAIL ### Request Body JSON credential payload to sign ```json { "payload": { "name": "Course credential", "description": "This credential shows that the person has attended the mention course and attained the relevant awards.", "@context": [ "https://optionalschema.example/" ], "type": [ "EducationalOccupationalCredential", "AlumniCredential" ], "credentialSubject": { "id": "did:example:abcdb1f712ebc6f1c276e12ec21", "givenName": "Jamie", "familyName": "Doe", "alumniOf": "Example University" }, "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "issuer": { "id": "did:issuer:abcdb1f712ebc6f1c276e12ec21", "name": "ABC University", "logoUrl": "https://example.edu/img/logo.png", "iconUrl": "https://example.edu/img/icon.png" }, "expirationDate": "2024-02-01T08:12:38.156Z", "issuanceDate": "2023-02-01T08:12:38.156Z" }, "proofType": "Ed25519Signature2018", "tag": "identifier123" } ``` ### Responses #### 200 - JSON Credential signed ```json { "id": "873277c0-a162-11ea-8a1d-a111119347e6", "credential": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://mattr.global/contexts/vc-extensions/v2", "https://w3id.org/vc-revocation-list-2020/v1", "https://optionalschema.example/" ], "type": [ "VerifiableCredential", "AlumniCredential" ], "issuanceDate": "2020-05-02T12:06:29.156Z", "credentialStatus": { "id": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3#1", "type": "RevocationList2020Status", "revocationListIndex": 1, "revocationListCredential": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3" }, "credentialSubject": { "givenName": "Jamie", "familyName": "Doe", "alumniOf": "Example University" }, "proof": { "type": "Ed25519Signature2018", "created": "2020-05-02T12:06:29Z", "jws": "EXAMPLE_JWS_TOKEN_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "proofPurpose": "assertionMethod", "verificationMethod": "did:web:organization.com" }, "name": "Alumni Credential", "description": "This credential shows that the person has attended the mentioned university." }, "tag": "identifier123", "credentialStatus": { "id": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3#1", "type": "RevocationList2020Status", "revocationListIndex": 1, "revocationListCredential": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3" }, "issuanceDate": "2020-05-02T12:06:29.156Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all credential data ## Endpoint ``` GET /v2/credentials/web-semantic ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns all available data for existing credentials: - For credentials that were created with the `persist` flag set to `true`, the response contains both the credential and its metadata. - For credentials that were created with the persist flag set to `false`, the response only contains the metadata (`id`, `tag`, `credentialStatus`, `issuanceDate`). ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_RETRIEVE_LIST_START * CREDENTIAL_WEB_SEMANTIC_RETRIEVE_LIST_SUCCESS * CREDENTIAL_WEB_SEMANTIC_RETRIEVE_LIST_FAIL ### Query Parameters - `tag`: string Optional tag to filter on. - `type`: string Optional credential type to filter on. - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Credentials data retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "873277c0-a162-11ea-8a1d-a111119347e6", "credential": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "type": [ "VerifiableCredential", "AlumniCredential" ], "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "issuanceDate": "2020-05-02T12:06:29.156Z", "credentialStatus": { "id": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3#1", "type": "RevocationList2020Status", "revocationListIndex": 1, "revocationListCredential": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3" }, "credentialSubject": { "givenName": "Jamie", "familyName": "Doe", "alumniOf": "Example University" }, "proof": { "type": "Ed25519Signature2018", "created": "2020-05-02T12:06:29Z", "jws": "EXAMPLE_JWS_TOKEN_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "proofPurpose": "assertionMethod", "verificationMethod": "did:web:organization.com" }, "name": "Alumni Credential", "description": "This credential shows that the person has attended the mentioned university." }, "tag": "identifier123", "credentialStatus": { "id": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3#1", "type": "RevocationList2020Status", "revocationListIndex": 1, "revocationListCredential": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3" }, "issuanceDate": "2020-05-02T12:06:29.156Z" } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve credential data ## Endpoint ``` GET /v2/credentials/web-semantic/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns all available data for an existing credential that matches the provided ID: - For credentials that were created with the `persist` flag set to `true`, the response contains both the credential and its metadata. - For credentials that were created with the persist flag set to `false`, the response only contains the metadata (`id`, `tag`, `credentialStatus`, `issuanceDate`) ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_RETRIEVE_START * CREDENTIAL_WEB_SEMANTIC_RETRIEVE_SUCCESS * CREDENTIAL_WEB_SEMANTIC_RETRIEVE_FAIL ### Responses #### 200 - Credential data retrieved ```json { "id": "873277c0-a162-11ea-8a1d-a111119347e6", "credential": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "type": [ "VerifiableCredential", "AlumniCredential" ], "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "issuanceDate": "2020-05-02T12:06:29.156Z", "credentialStatus": { "id": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3#1", "type": "RevocationList2020Status", "revocationListIndex": 1, "revocationListCredential": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3" }, "credentialSubject": { "givenName": "Jamie", "familyName": "Doe", "alumniOf": "Example University" }, "proof": { "type": "Ed25519Signature2018", "created": "2020-05-02T12:06:29Z", "jws": "EXAMPLE_JWS_TOKEN_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "proofPurpose": "assertionMethod", "verificationMethod": "did:web:organization.com" }, "name": "Alumni Credential", "description": "This credential shows that the person has attended the mentioned university." }, "tag": "identifier123", "credentialStatus": { "id": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3#1", "type": "RevocationList2020Status", "revocationListIndex": 1, "revocationListCredential": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3" }, "issuanceDate": "2020-05-02T12:06:29.156Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete credential data ## Endpoint ``` DELETE /v2/credentials/web-semantic/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes all stored data for an existing credential that matches the provided ID. If the credential is revocable, it will also be permanently revoked. Removed credential data cannot be recovered. ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_DELETE_START * CREDENTIAL_WEB_SEMANTIC_DELETE_SUCCESS * CREDENTIAL_WEB_SEMANTIC_DELETE_FAIL ### Responses #### 204 - Credential deleted and revoked if revocable #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve credential revocation status ## Endpoint ``` GET /v2/credentials/web-semantic/{id}/revocation-status ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/{id}/revocation-status` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns the revocation status of the credential matching the provided ID. ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_REVOCATION_RETRIEVE_START * CREDENTIAL_WEB_SEMANTIC_REVOCATION_RETRIEVE_SUCCESS * CREDENTIAL_WEB_SEMANTIC_REVOCATION_RETRIEVE_FAIL ### Responses #### 200 - Credential status ```json { "isRevoked": false } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Set credential revocation status ## Endpoint ``` POST /v2/credentials/web-semantic/{id}/revocation-status ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/{id}/revocation-status` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Sets the revocation status of the credential that matches the provided ID as `true` (revoked) or `false` (unrevoked). ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_REVOCATION_SET_STATUS_START * CREDENTIAL_WEB_SEMANTIC_REVOCATION_SET_STATUS_SUCCESS * CREDENTIAL_WEB_SEMANTIC_REVOCATION_SET_STATUS_FAIL ### Request Body Setting the revocation status ```json { "isRevoked": false } ``` ### Responses #### 200 - Revocation status updated #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve revocation list ## Endpoint ``` GET /v2/credentials/web-semantic/revocation-lists/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/revocation-lists/{id}` ### Authorization None required. ## Description Retrieves a JSON credential revocation list by providing its ID. This endpoint is intended for public consumption, and as such does not require authentication. ### Responses #### 200 - Revocation list retrieved ```json { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "type": [ "VerifiableCredential", "AlumniCredential" ], "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "issuanceDate": "2020-05-02T12:06:29.156Z", "credentialStatus": { "id": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3#1", "type": "RevocationList2020Status", "revocationListIndex": 1, "revocationListCredential": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3" }, "credentialSubject": { "givenName": "Jamie", "familyName": "Doe", "alumniOf": "Example University" }, "proof": { "type": "Ed25519Signature2018", "created": "2020-05-02T12:06:29Z", "jws": "EXAMPLE_JWS_TOKEN_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "proofPurpose": "assertionMethod", "verificationMethod": "did:web:organization.com" }, "name": "Alumni Credential", "description": "This credential shows that the person has attended the mentioned university." } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a revocation message payload ## Endpoint ``` POST /v2/credentials/web-semantic/{id}/revocation-status/notification ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/{id}/revocation-status/notification` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a message in JWM format that can be used to notify subjects based on their credential revocation status change. To send a notification to the Subject DID holder, use the returned payload with the [encrypt](#operation/encryptMessage) and [send](#operation/sendMessage) endpoints. ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_REVOCATION_MESSAGE_PAYLOAD_CREATE_START * CREDENTIAL_WEB_SEMANTIC_REVOCATION_MESSAGE_PAYLOAD_CREATE_SUCCESS * CREDENTIAL_WEB_SEMANTIC_REVOCATION_MESSAGE_PAYLOAD_CREATE_FAIL ### Request Body Create a JWM message payload ```json { "from": "did:web:organization.com", "to": [ "did:key:subjectDid1", "did:key:subjectDid2", "did:key:subjectDid3" ] } ``` ### Responses #### 201 - Revocation message payload created ```json { "to": [ "did:key:subjectDid1", "did:key:subjectDid2", "did:key:subjectDid3" ], "from": "did:web:organization.com" } ``` # Verify a JSON credential ## Endpoint ``` POST /v2/credentials/web-semantic/verify ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/verify` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Verify a JSON credential by providing its payload. The credential is verified against the following criteria: - Issuer DID can be resolved, so that the referenced DID Document is available and valid and the public key is obtainable. - Proof is valid and the credential has not been tampered with. - JSON-LD context is valid for subject claims. Optional verification checks: - If `assertExpiry` is set to `true` and the credential has a set expiration date, verification will fail if the expiration date has passed. - If `checkRevocation` is set to `true` and the provided credential contains a revocation status list, verification will fail if the credential has been set to `revoked`. ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_VERIFY_START * CREDENTIAL_WEB_SEMANTIC_VERIFY_SUCCESS * CREDENTIAL_WEB_SEMANTIC_VERIFY_FAIL ### Request Body ```json { "payload": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://mattr.global/contexts/vc-extensions/v2", "https://w3id.org/vc-revocation-list-2020/v1", "https://optionalschema.example/" ], "type": [ "VerifiableCredential", "AlumniCredential" ], "issuanceDate": "2020-05-02T12:06:29.156Z", "credentialStatus": { "id": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3#1", "type": "RevocationList2020Status", "revocationListIndex": 1, "revocationListCredential": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3" }, "credentialSubject": { "givenName": "Jamie", "familyName": "Doe", "alumniOf": "Example University" }, "proof": { "type": "Ed25519Signature2018", "created": "2020-05-02T12:06:29Z", "jws": "EXAMPLE_JWS_TOKEN_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "proofPurpose": "assertionMethod", "verificationMethod": "did:web:organization.com" }, "name": "Alumni Credential", "description": "This credential shows that the person has attended the mentioned university." } } ``` ### Responses #### 200 - Verification completed #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all presentation templates ## Endpoint ``` GET /v2/credentials/web-semantic/presentations/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/presentations/templates` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Returns a list of all presentation templates on your tenant. ### **Analytic events** * PRESENTATION_WEB_SEMANTIC_TEMPLATE_RETRIEVE_LIST_START * PRESENTATION_WEB_SEMANTIC_TEMPLATE_RETRIEVE_LIST_SUCCESS * PRESENTATION_WEB_SEMANTIC_TEMPLATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Presentation templates retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "64e45290-9980-11ea-b872-f1bee5fb328f", "domain": "tenant.vii.mattr.global", "name": "alumni_credential_request" } ] } ``` # Create a presentation template ## Endpoint ``` POST /v2/credentials/web-semantic/presentations/templates ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/presentations/templates` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Creates a presentation template defining what type of credential is required for a particular verification workflow. Presentation templates are used to create presentation requests that are shared with a specific holder. ### **Analytic events** * PRESENTATION_WEB_SEMANTIC_TEMPLATE_CREATE_START * PRESENTATION_WEB_SEMANTIC_TEMPLATE_CREATE_SUCCESS * PRESENTATION_WEB_SEMANTIC_TEMPLATE_CREATE_FAIL ### Request Body The template ### Responses #### 201 - Presentation template created ```json { "id": "64e45290-9980-11ea-b872-f1bee5fb328f", "domain": "tenant.vii.mattr.global", "name": "alumni_credential_request" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a presentation template ## Endpoint ``` GET /v2/credentials/web-semantic/presentations/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/presentations/templates/{id}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Retrieve an existing presentation template by its ID. ### **Analytic events** * PRESENTATION_WEB_SEMANTIC_TEMPLATE_RETRIEVE_START * PRESENTATION_WEB_SEMANTIC_TEMPLATE_RETRIEVE_SUCCESS * PRESENTATION_WEB_SEMANTIC_TEMPLATE_RETRIEVE_FAIL ### Responses #### 200 - Presentation template retrieved ```json { "id": "64e45290-9980-11ea-b872-f1bee5fb328f", "domain": "tenant.vii.mattr.global", "name": "alumni_credential_request" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a presentation template ## Endpoint ``` DELETE /v2/credentials/web-semantic/presentations/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/presentations/templates/{id}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Deletes an existing presentation template by its ID. ### **Analytic events** * PRESENTATION_WEB_SEMANTIC_TEMPLATE_DELETE_START * PRESENTATION_WEB_SEMANTIC_TEMPLATE_DELETE_SUCCESS * PRESENTATION_WEB_SEMANTIC_TEMPLATE_DELETE_FAIL ### Responses #### 204 - Presentation template deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a presentation template ## Endpoint ``` PUT /v2/credentials/web-semantic/presentations/templates/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/presentations/templates/{id}` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Updates an existing presentation template by its ID. ### **Analytic events** * PRESENTATION_WEB_SEMANTIC_TEMPLATE_UPDATE_START * PRESENTATION_WEB_SEMANTIC_TEMPLATE_UPDATE_SUCCESS * PRESENTATION_WEB_SEMANTIC_TEMPLATE_UPDATE_FAIL ### Request Body ### Responses #### 200 - OK ```json { "id": "64e45290-9980-11ea-b872-f1bee5fb328f", "domain": "tenant.vii.mattr.global", "name": "alumni_credential_request" } ``` # Create a presentation request ## Endpoint ``` POST /v2/credentials/web-semantic/presentations/requests ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/presentations/requests` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Creates a short lived presentation request based on an existing presentation template. The request is returned in the form of a JWM message and must be [signed](#operation/signMessage) and sent to the holder via one of the following methods: - QR code. - Deeplink. - [Encrypted](#operation/encryptMessage) and [sent](#operation/sendMessage) as a wallet notification. ### **Analytic events** * PRESENTATION_WEB_SEMANTIC_REQUEST_CREATE_START * PRESENTATION_WEB_SEMANTIC_REQUEST_CREATE_SUCCESS * PRESENTATION_WEB_SEMANTIC_REQUEST_CREATE_FAIL ### Request Body The presentation request payload ```json { "challenge": "64e45290-9980-11ea-b872-f1bee5fb328f", "did": "did:key:z6Mkt7bFYc4V2HdAxwhMtaY6cgJckYXwhYdPLJCcnVqzrkpr", "templateId": "64e45290-9980-11ea-b872-f1bee5fb328f", "expiresTime": 1592955632103, "callbackUrl": "https://your-website.com/api/callback" } ``` ### Responses #### 201 - Presentation request created ```json { "id": "c74128a0-9949-11ea-9554-b5a630b3c119", "callbackUrl": "https://your-website.com/api/callback", "request": { "id": "c74128a0-9949-11ea-9554-b5a630b3c119", "type": "https://mattr.global/schemas/verifiable-presentation/request/QueryByExample", "from": "did:key:z6MkrYVmyqSA93o4B1GwERM8kaQDMAUKAFV2TC3weQKeg9Gq", "created_time": 1606709582907, "expires_time": 2594859115000, "reply_url": "https://your-website.com/api/callback", "reply_to": [ "did:key:z6MkrYVmyqSA93o4B1GwERM8kaQDMAUKAFV2TC3weQKeg9Gq" ], "body": { "id": "64e45290-9980-11ea-b872-f1bee5fb328f", "domain": "tenant.vii.mattr.global", "name": "alumni_credential_request", "challenge": "e1b35ae0-9e0e-11ea-9bbf-a387b27c9e60" } } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Verify a verifiable presentation ## Endpoint ``` POST /v2/credentials/web-semantic/presentations/verify ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/presentations/verify` ### Authorization Bearer token required. Required roles: admin, verifier ## Description Verifies a provided verifiable presentation that adheres to the [W3C Verifiable Credential Data Model](https://www.w3.org/TR/vc-data-model/#presentations): - Ensures the presentation conforms to the VC Data model. - For each `verifiableCredential` objects: - Issuer DID can be resolved. - JSON-LD context is valid for subject claims. - Proof is valid & the credential has not been tampered with. - Is not in a `revoked` status on a `RevocationList2020`. - The proof is valid for each subjectDID to prove ownership. - Valid proof exists for the presentation `holderDID`. The request must include a `presentation` object that adheres to the [W3C Verifiable Credential Data Model](https://www.w3.org/TR/vc-data-model/#presentations). If a `challenge` and/or `domain` is provided they are used for credential verification. Otherwise, the `challenge` and/or `domain` within the presentation proof is used instead. ### **Analytic events** * PRESENTATION_WEB_SEMANTIC_VERIFY_START * PRESENTATION_WEB_SEMANTIC_VERIFY_SUCCESS * PRESENTATION_WEB_SEMANTIC_VERIFY_FAIL ### Request Body Presentation to verify ```json { "presentation": { "verifiableCredential": [ { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1" ], "type": [ "VerifiableCredential", "AlumniCredential" ], "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "issuanceDate": "2020-05-02T12:06:29.156Z", "credentialStatus": { "id": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3#1", "type": "RevocationList2020Status", "revocationListIndex": 1, "revocationListCredential": "https://tenant.vii.mattr.global/v1/revocation-lists/cc641396-3750-43c8-b8b8-f30d74eb3fb3" }, "credentialSubject": { "givenName": "Jamie", "familyName": "Doe", "alumniOf": "Example University" }, "proof": { "type": "Ed25519Signature2018", "created": "2020-05-02T12:06:29Z", "jws": "EXAMPLE_JWS_TOKEN_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "proofPurpose": "assertionMethod", "verificationMethod": "did:web:organization.com" }, "name": "Alumni Credential", "description": "This credential shows that the person has attended the mentioned university." } ] }, "challenge": "3182bdea-63d9-11ea-b6de-3b7c1404d57f", "domain": "example.com" } ``` ### Responses #### 200 - Presentation verification completed #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all JSON credentials configurations ## Endpoint ``` GET /v2/credentials/web-semantic/configurations ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/configurations` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a list of all JSON credential configurations on your tenant. ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_START * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_SUCCESS * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. - `type`: string The optional credential type to filter on ### Responses #### 200 - JSON credentials configurations retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "983c0a86-204f-4431-9371-f5a22e506599", "name": "Course credential", "description": "This credential shows that the person has attended a course.", "type": "CourseCredential", "additionalTypes": [ "AlumniCredential", "EducationCredential" ], "contexts": [ "https://optionalschema.example/" ], "issuer": { "name": "ABC University", "logoUrl": "https://example.edu/img/logo.png", "iconUrl": "https://example.edu/img/icon.png" }, "proofType": "Ed25519Signature2018", "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "months": 3 } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a JSON credentials configuration ## Endpoint ``` POST /v2/credentials/web-semantic/configurations ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/configurations` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Creates a new JSON credentials configuration, a specific set of rules and parameters that are used to create and validate a particular type of verifiable credential. These rules and parameters define how the credential is structured and what data it contains when issued. ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_CREATE_START * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_CREATE_SUCCESS * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_CREATE_FAIL ### Request Body The credential configuration payload ```json { "name": "Course credential", "description": "This credential shows that the person has attended a course.", "type": "CourseCredential", "additionalTypes": [ "AlumniCredential", "EducationCredential" ], "contexts": [ "https://optionalschema.example/" ], "issuer": { "name": "ABC University", "logoUrl": "https://example.edu/img/logo.png", "iconUrl": "https://example.edu/img/icon.png" }, "proofType": "Ed25519Signature2018", "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "months": 3 } } ``` ### Responses #### 201 - JSON credentials configuration created ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "name": "Course credential", "description": "This credential shows that the person has attended a course.", "type": "CourseCredential", "additionalTypes": [ "AlumniCredential", "EducationCredential" ], "contexts": [ "https://optionalschema.example/" ], "issuer": { "name": "ABC University", "logoUrl": "https://example.edu/img/logo.png", "iconUrl": "https://example.edu/img/icon.png" }, "proofType": "Ed25519Signature2018", "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "months": 3 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a JSON credentials configuration ## Endpoint ``` GET /v2/credentials/web-semantic/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieve a JSON credentials configuration by providing its ID. ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_START * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_SUCCESS * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_RETRIEVE_FAIL ### Responses #### 200 - JSON credentials configuration retrieved ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "name": "Course credential", "description": "This credential shows that the person has attended a course.", "type": "CourseCredential", "additionalTypes": [ "AlumniCredential", "EducationCredential" ], "contexts": [ "https://optionalschema.example/" ], "issuer": { "name": "ABC University", "logoUrl": "https://example.edu/img/logo.png", "iconUrl": "https://example.edu/img/icon.png" }, "proofType": "Ed25519Signature2018", "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "months": 3 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a JSON credentials configuration ## Endpoint ``` DELETE /v2/credentials/web-semantic/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Deletes an existing JSON credentials configuration by providing its ID. ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_DELETE_START * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_DELETE_SUCCESS * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_DELETE_FAIL ### Responses #### 204 - JSON credentials configuration deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - The credential configuration is not found ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a JSON credentials configuration ## Endpoint ``` PUT /v2/credentials/web-semantic/configurations/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v2/credentials/web-semantic/configurations/{id}` ### Authorization Bearer token required. Required roles: admin, issuer ## Description Updates an existing JSON credentials configuration by providing its ID. ### **Analytic events** * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_UPDATE_START * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_UPDATE_SUCCESS * CREDENTIAL_WEB_SEMANTIC_CREDENTIAL_CONFIGURATION_UPDATE_FAIL ### Request Body Update a credential configuration ```json { "name": "Course credential", "description": "This credential shows that the person has attended a course.", "type": "CourseCredential", "additionalTypes": [ "AlumniCredential", "EducationCredential" ], "contexts": [ "https://optionalschema.example/" ], "issuer": { "name": "ABC University", "logoUrl": "https://example.edu/img/logo.png", "iconUrl": "https://example.edu/img/icon.png" }, "proofType": "Ed25519Signature2018", "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "months": 3 } } ``` ### Responses #### 200 - JSON credentials configuration updated ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "name": "Course credential", "description": "This credential shows that the person has attended a course.", "type": "CourseCredential", "additionalTypes": [ "AlumniCredential", "EducationCredential" ], "contexts": [ "https://optionalschema.example/" ], "issuer": { "name": "ABC University", "logoUrl": "https://example.edu/img/logo.png", "iconUrl": "https://example.edu/img/icon.png" }, "proofType": "Ed25519Signature2018", "credentialBranding": { "backgroundColor": "#B00AA0", "watermarkImageUrl": "https://example.edu/img/watermark.png" }, "claimMappings": { "firstName": { "mapFrom": "claims.given_name", "required": true }, "address": { "mapFrom": "claims.address.formatted" }, "picture": { "mapFrom": "claims.picture", "defaultValue": "http://example.edu/img/placeholder.png" }, "badge": { "defaultValue": "http://example.edu/img/badge.png" }, "providerSubjectId": { "mapFrom": "authenticationProvider.subjectId" } }, "expiresIn": { "months": 3 } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all holder applications ## Endpoint ``` GET /v1/holder/applications ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/applications` ### Authorization Bearer token required. Required roles: admin, holder ## Description Retrieves all configured holder applications. ### **Analytic events** * CREDENTIAL_HOLDER_APPLICATION_RETRIEVE_LIST_START * CREDENTIAL_HOLDER_APPLICATION_RETRIEVE_LIST_SUCCESS * CREDENTIAL_HOLDER_APPLICATION_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Holder applications retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "name": "Example Holder Application", "clientId": "example-wallet-client" } ] } ``` # Create a holder application ## Endpoint ``` POST /v1/holder/applications ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/applications` ### Authorization Bearer token required. Required roles: admin, holder ## Description Creates a holder application. Once created, application instances can be registered and interact with the MATTR VII platform (for example, to claim credentials). ### **Analytic events** * CREDENTIAL_HOLDER_APPLICATION_CREATE_START * CREDENTIAL_HOLDER_APPLICATION_CREATE_SUCCESS * CREDENTIAL_HOLDER_APPLICATION_CREATE_FAIL ### Request Body Holder application payload ```json { "name": "My Holder Application", "clientId": "example-wallet-client" } ``` ### Responses #### 201 - Holder application created ```json { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "name": "Example Holder Application", "clientId": "example-wallet-client" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a holder application ## Endpoint ``` GET /v1/holder/applications/{applicationId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/applications/{applicationId}` ### Authorization Bearer token required. Required roles: admin, holder ## Description Retrieves an existing holder application. ### **Analytic events** * CREDENTIAL_HOLDER_APPLICATION_RETRIEVE_START * CREDENTIAL_HOLDER_APPLICATION_RETRIEVE_SUCCESS * CREDENTIAL_HOLDER_APPLICATION_RETRIEVE_FAIL ### Responses #### 200 - Holder application retrieved ```json { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "name": "Example Holder Application", "clientId": "example-wallet-client" } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a holder application ## Endpoint ``` DELETE /v1/holder/applications/{applicationId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/applications/{applicationId}` ### Authorization Bearer token required. Required roles: admin, holder ## Description Deletes an existing holder application. Once deleted, any associated application instances will no longer be able to interact with the MATTR VII platform. ### **Analytic events** * CREDENTIAL_HOLDER_APPLICATION_DELETE_START * CREDENTIAL_HOLDER_APPLICATION_DELETE_SUCCESS * CREDENTIAL_HOLDER_APPLICATION_DELETE_FAIL ### Responses #### 204 - Holder application deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a holder application ## Endpoint ``` PUT /v1/holder/applications/{applicationId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/applications/{applicationId}` ### Authorization Bearer token required. Required roles: admin, holder ## Description Updates an existing holder application. ### **Analytic events** * CREDENTIAL_HOLDER_APPLICATION_UPDATE_START * CREDENTIAL_HOLDER_APPLICATION_UPDATE_SUCCESS * CREDENTIAL_HOLDER_APPLICATION_UPDATE_FAIL ### Request Body Holder application payload ```json { "name": "My Holder Application", "clientId": "example-wallet-client" } ``` ### Responses #### 200 - Holder application updated ```json { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "name": "Example Holder Application", "clientId": "example-wallet-client" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all registered holder application instances ## Endpoint ``` GET /v1/holder/applications/{applicationId}/instances ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/applications/{applicationId}/instances` ### Authorization Bearer token required. Required roles: admin, holder ## Description Retrieves all registered instances of a holder application. This can be used by application owners to view all registered instances for a given holder application. ### **Analytic events** * CREDENTIAL_HOLDER_APPLICATION_INSTANCE_RETRIEVE_LIST_START * CREDENTIAL_HOLDER_APPLICATION_INSTANCE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_HOLDER_APPLICATION_INSTANCE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Holder application instances retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "appAttestationType": "key_attestation", "registeredAt": "2023-10-05T14:48:00.000Z", "licenseExpiresAt": "2024-10-05T14:48:00.000Z", "lastAttestedAt": "2023-12-01T10:30:00.000Z", "externalReferenceId": "external-ref-12345", "deviceDetails": { "deviceModel": "iPhone 12", "deviceMake": "Apple", "osVersion": "iOS 14.4" }, "sdkDetails": { "sdkVersion": "1.2.3" } } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a registered holder application instance ## Endpoint ``` GET /v1/holder/applications/{applicationId}/instances/{instanceId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/applications/{applicationId}/instances/{instanceId}` ### Authorization Bearer token required. Required roles: admin, holder ## Description Retrieves a registered instance of a holder application. ### **Analytic events** * CREDENTIAL_HOLDER_APPLICATION_INSTANCE_RETRIEVE_START * CREDENTIAL_HOLDER_APPLICATION_INSTANCE_RETRIEVE_SUCCESS * CREDENTIAL_HOLDER_APPLICATION_INSTANCE_RETRIEVE_FAIL ### Responses #### 200 - Holder application instance retrieved ```json { "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", "appAttestationType": "key_attestation", "registeredAt": "2023-10-05T14:48:00.000Z", "licenseExpiresAt": "2024-10-05T14:48:00.000Z", "lastAttestedAt": "2023-12-01T10:30:00.000Z", "externalReferenceId": "external-ref-12345", "deviceDetails": { "deviceModel": "iPhone 12", "deviceMake": "Apple", "osVersion": "iOS 14.4" }, "sdkDetails": { "sdkVersion": "1.2.3" } } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a registered holder application instance ## Endpoint ``` DELETE /v1/holder/applications/{applicationId}/instances/{instanceId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/applications/{applicationId}/instances/{instanceId}` ### Authorization Bearer token required. Required roles: admin, holder ## Description Deletes a registered instance of a holder application. Deleted instances will no longer be able to interact with the platform or receive tokens, and any existing tokens will be revoked. Application owners can use this endpoint to remove individual instances that are no longer needed or were registered by mistake, without affecting the entire holder application or its other instances. This is useful for cleaning up test instances or managing specific devices, rather than deleting the whole application and all its associated instances. ### **Analytic events** * CREDENTIAL_HOLDER_APPLICATION_INSTANCE_DELETE_START * CREDENTIAL_HOLDER_APPLICATION_INSTANCE_DELETE_SUCCESS * CREDENTIAL_HOLDER_APPLICATION_INSTANCE_DELETE_FAIL ### Responses #### 204 - Holder application instance deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all holder root CA certificates ## Endpoint ``` GET /v1/holder/certificates/ca ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/ca` ### Authorization Bearer token required. Required roles: admin, holder ## Description Retrieves all holder root CA certificates for the tenant. ### **Analytic events** * CREDENTIAL_HOLDER_CA_CERTIFICATE_RETRIEVE_LIST_START * CREDENTIAL_HOLDER_CA_CERTIFICATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_HOLDER_CA_CERTIFICATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Holder root CA certificates retrieved. ```json { "data": [ { "id": "281d20b3-42a3-40dd-b29a-115ff32b02b7", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "a3b2c1d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890", "certificateData": { "commonName": "Example Tenant Wallet Attestation Root", "country": "NZ", "notBefore": "2026-04-06T00:00:00.000Z", "notAfter": "2036-04-06T00:00:00.000Z" }, "isManaged": true } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a holder root CA certificate ## Endpoint ``` POST /v1/holder/certificates/ca ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/ca` ### Authorization Bearer token required. Required roles: admin, holder ## Description Creates a holder root CA certificate that is used to issue wallet attestation signer certificates. Two flows are supported: - **Managed** — MATTR VII generates the root certificate and manages the private key on the customer's behalf. Supply no `certificatePem` in the request body; `commonName` and `country` are optional. - **Unmanaged** — the customer supplies their own externally-managed root CA in PEM format. `commonName` and `country` are extracted from the certificate and must not be provided in the request. A maximum of three holder root CA certificates can be created per tenant. Only one can be active at a time. The newly-created root is always inactive. Activate it by issuing `PUT /v1/holder/certificates/ca/{certificateId}` with `{ "active": true }`, which also deactivates any previously active root for the tenant (single-active constraint). ### **Analytic events** * CREDENTIAL_HOLDER_CA_CERTIFICATE_CREATE_START * CREDENTIAL_HOLDER_CA_CERTIFICATE_CREATE_SUCCESS * CREDENTIAL_HOLDER_CA_CERTIFICATE_CREATE_FAIL ### Request Body ### Responses #### 201 - Holder root CA certificate created. ```json { "id": "281d20b3-42a3-40dd-b29a-115ff32b02b7", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "a3b2c1d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890", "certificateData": { "commonName": "Example Tenant Wallet Attestation Root", "country": "NZ", "notBefore": "2026-04-06T00:00:00.000Z", "notAfter": "2036-04-06T00:00:00.000Z" }, "isManaged": true } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 409 - Maximum number of holder root CA certificates reached. Delete an existing certificate before creating a new one. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 503 - Service Unavailable. The server is temporarily unavailable to handle requests. # Retrieve a holder root CA certificate ## Endpoint ``` GET /v1/holder/certificates/ca/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/ca/{certificateId}` ### Authorization Bearer token required. Required roles: admin, holder ## Description Retrieves a holder root CA certificate by ID. ### **Analytic events** * CREDENTIAL_HOLDER_CA_CERTIFICATE_RETRIEVE_START * CREDENTIAL_HOLDER_CA_CERTIFICATE_RETRIEVE_SUCCESS * CREDENTIAL_HOLDER_CA_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - Holder root CA certificate retrieved. ```json { "id": "281d20b3-42a3-40dd-b29a-115ff32b02b7", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "a3b2c1d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890", "certificateData": { "commonName": "Example Tenant Wallet Attestation Root", "country": "NZ", "notBefore": "2026-04-06T00:00:00.000Z", "notAfter": "2036-04-06T00:00:00.000Z" }, "isManaged": true } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a holder root CA certificate ## Endpoint ``` DELETE /v1/holder/certificates/ca/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/ca/{certificateId}` ### Authorization Bearer token required. Required roles: admin, holder ## Description Deletes a holder root CA certificate and cascade-deletes all associated signer certificates. For managed roots and signers, MATTR VII also removes the private key material it was holding on the customer's behalf. Deletion does not invalidate any wallet attestation JWTs that were issued under this root. Those JWTs remain cryptographically valid until their natural expiry — verifiers that have already cached the signer's public key may continue to accept them. CRL-based revocation of issued attestations is not supported in this release. ### **Analytic events** * CREDENTIAL_HOLDER_CA_CERTIFICATE_DELETE_START * CREDENTIAL_HOLDER_CA_CERTIFICATE_DELETE_SUCCESS * CREDENTIAL_HOLDER_CA_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - Holder root CA certificate deleted. #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 503 - Service Unavailable. The server is temporarily unavailable to handle requests. # Update a holder root CA certificate ## Endpoint ``` PUT /v1/holder/certificates/ca/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/ca/{certificateId}` ### Authorization Bearer token required. Required roles: admin, holder ## Description Updates a holder root CA certificate. The only mutable field is `active`. Setting `active: true` deactivates all other roots for the tenant (single-active constraint). ### **Analytic events** * CREDENTIAL_HOLDER_CA_CERTIFICATE_UPDATE_START * CREDENTIAL_HOLDER_CA_CERTIFICATE_UPDATE_SUCCESS * CREDENTIAL_HOLDER_CA_CERTIFICATE_UPDATE_FAIL ### Request Body ```json { "active": true } ``` ### Responses #### 200 - Holder root CA certificate updated. ```json { "id": "281d20b3-42a3-40dd-b29a-115ff32b02b7", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "a3b2c1d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890", "certificateData": { "commonName": "Example Tenant Wallet Attestation Root", "country": "NZ", "notBefore": "2026-04-06T00:00:00.000Z", "notAfter": "2036-04-06T00:00:00.000Z" }, "isManaged": true } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a holder root CA certificate revocation list ## Endpoint ``` GET /v1/holder/certificates/ca/{certificateId}/crl ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/ca/{certificateId}/crl` ### Authorization None required. ## Description Retrieves the Certificate Revocation List (CRL) for a managed holder root CA certificate, as a DER-encoded binary document. This endpoint is only available for managed roots — for unmanaged roots it returns `404 NoCertificateRevocationList`. ### Responses #### 200 - Holder root CA certificate revocation list retrieved. #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 503 - Service Unavailable. The server is temporarily unavailable to handle requests. # Retrieve all wallet attestation signers ## Endpoint ``` GET /v1/holder/certificates/wallet-attestation-signers ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/wallet-attestation-signers` ### Authorization Bearer token required. Required roles: admin ## Description Retrieves all wallet attestation signers for the tenant across all roots. The response may contain a mix of: - CSR-pending signers (unmanaged, certificate not yet uploaded) - Active signers (managed or unmanaged with an uploaded certificate) ### **Analytic events** * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_RETRIEVE_LIST_START * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_RETRIEVE_LIST_SUCCESS * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Wallet attestation signers retrieved. ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a wallet attestation signer ## Endpoint ``` POST /v1/holder/certificates/wallet-attestation-signers ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/wallet-attestation-signers` ### Authorization Bearer token required. Required roles: admin ## Description Creates a wallet attestation signer for an unmanaged root CA and returns its Certificate Signing Request (CSR). This endpoint is only available for unmanaged roots — managed root signers are auto-provisioned on demand during the first wallet attestation request and never need to be created explicitly. The returned signer is created with `active: false`; use the CSR to obtain a signed certificate externally and upload it via `PUT /v1/holder/certificates/wallet-attestation-signers/{certificateId}` to activate the signer. A maximum of five wallet attestation signers can be created per root. ### **Analytic events** * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_CREATE_START * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_CREATE_SUCCESS * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_CREATE_FAIL ### Request Body ```json { "caId": "281d20b3-42a3-40dd-b29a-115ff32b02b7" } ``` ### Responses #### 201 - Wallet attestation signer created. ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "caId": "281d20b3-42a3-40dd-b29a-115ff32b02b7", "csrPem": "-----BEGIN CERTIFICATE REQUEST-----\nMIIC5zCCAc8CAQAwgaExC...\n-----END CERTIFICATE REQUEST-----", "active": false } ``` #### 400 - Bad request. The request was malformed, missing required parameters, or the target root is managed (managed signers are auto-provisioned and cannot be created via this endpoint). ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 409 - Maximum number of wallet attestation signers for this root has been reached. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 503 - Service Unavailable. The server is temporarily unavailable to handle requests. # Retrieve a wallet attestation signer ## Endpoint ``` GET /v1/holder/certificates/wallet-attestation-signers/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/wallet-attestation-signers/{certificateId}` ### Authorization Bearer token required. Required roles: admin ## Description Retrieves a wallet attestation signer by ID. The response shape depends on the signer's state — CSR-pending signers return a CSR, signers with a signed certificate return the full certificate details. ### **Analytic events** * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_RETRIEVE_START * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_RETRIEVE_SUCCESS * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - Wallet attestation signer retrieved. #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a wallet attestation signer ## Endpoint ``` DELETE /v1/holder/certificates/wallet-attestation-signers/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/wallet-attestation-signers/{certificateId}` ### Authorization Bearer token required. Required roles: admin ## Description Deletes a wallet attestation signer. For managed signers, MATTR VII also removes the private key material it was holding on the customer's behalf. ### **Analytic events** * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_DELETE_START * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_DELETE_SUCCESS * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - Wallet attestation signer deleted. #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 503 - Service Unavailable. The server is temporarily unavailable to handle requests. # Update a wallet attestation signer ## Endpoint ``` PUT /v1/holder/certificates/wallet-attestation-signers/{certificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/holder/certificates/wallet-attestation-signers/{certificateId}` ### Authorization Bearer token required. Required roles: admin ## Description Updates a wallet attestation signer by: - Uploading a signed certificate PEM that matches the signer's CSR (first-time upload for CSR-pending signers only). - Activating or deactivating the signer. Only signers with a valid PEM certificate can be activated. `certificatePem` is immutable after the first upload — subsequent PUT requests may only toggle `active`. ### **Analytic events** * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_UPDATE_START * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_UPDATE_SUCCESS * CREDENTIAL_HOLDER_WALLET_ATTESTATION_SIGNER_CERTIFICATE_UPDATE_FAIL ### Request Body ```json { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----" } ``` ### Responses #### 200 - Wallet attestation signer updated. ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "caId": "281d20b3-42a3-40dd-b29a-115ff32b02b7", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com Wallet Attestation Signer", "country": "NZ", "notBefore": "2026-04-06T00:00:00.000Z", "notAfter": "2027-04-06T00:00:00.000Z" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Request authorization for access to resources ## Endpoint ``` GET /v1/oauth/authorize ``` Full URL: `https://example.vii.au01.mattr.global/v1/oauth/authorize` ### Authorization None required. ## Description This endpoint is used to request authorization from the user for access to the requested resources. After the user approves the request, an authorization code is returned to the client. See [https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-authorization-endpoint](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-authorization-endpoint) See [https://www.rfc-editor.org/rfc/rfc6749.html#section-3.1](https://www.rfc-editor.org/rfc/rfc6749.html#section-3.1) ### **Analytic events** * OPENID_AUTHORIZE_START * OPENID_AUTHORIZE_SUCCESS * OPENID_AUTHORIZE_FAIL ### Query Parameters - `response_type`: string (required) The response type, which must be 'code'. - `client_id`: string (required) The client identifier. - `redirect_uri`: string (required) The URI to which the authorization server will redirect the user-agent with the authorization code. - `scope`: string (required) The scope of the access request. - `state`: string An opaque value used by the client to maintain state between the request and callback. - `code_challenge_method`: string (required) The method used to derive the code_challenge, which must be 'S256'. - `code_challenge`: string (required) A high entropy random challenge generated by the client. ### Responses #### 302 - Redirection to client application with authorization code #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 500 - Internal Server Error. An unexpected error occurred. # Exchange authorization code for access token ## Endpoint ``` POST /v1/oauth/token ``` Full URL: `https://example.vii.au01.mattr.global/v1/oauth/token` ### Authorization None required. ## Description This endpoint is used to exchange an authorization code or a pre-authorized code for an access token, which is later used to request a credential. - In an Authorization Code flow the authorization code is obtained from the authorization endpoint after the user has successfully authenticated. - In a Pre-authorized Code flow the pre-authorized code is obtained from the offer URI. See [https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-token-endpoint](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-token-endpoint) for more information. ### **Analytic events** * OPENID_TOKEN_START * OPENID_TOKEN_SUCCESS * OPENID_TOKEN_FAIL ### Request Body ### Responses #### 200 - Access token successfully returned. #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 500 - Internal Server Error. An unexpected error occurred. # Issue a verifiable credential ## Endpoint ``` POST /v1/openid/credential ``` Full URL: `https://example.vii.au01.mattr.global/v1/openid/credential` ### Authorization Bearer token required. ## Description Issues a credential to a holder upon presentation of a valid access token, as per [OID4VCI](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-endpoint). Supports [encrypted](/docs/issuance/credential-issuance/e2e-encryption) and non-encrypted credential issuance. Encrypted credential issuance is currently in technical preview and must be enabled on a per-tenant basis. If you would like to enable this feature for your tenant, please [contact us](mailto:dev-support@mattr.global). For non-encrypted credential issuance the valid access token must be provided in one of the following header formats: **Bearer** - Authorization: `Bearer `. - Content-Type: `application/json` - Body: `` OR **DPoP** Only required when using DPoP-bound access tokens. - Authorization: `DPoP `. - DPoP: `` - Content-Type: `application/json` - Body: `` For encrypted credential issuance, [contact us](mailto:dev-support@mattr.global) to configure how your MATTR VII enforces request and/or response encryption, and then: - For **request** encryption, `Content-Type` must be set to `application/jwt` and the payload must be JWE formatted. - For **response** encryption, include the [`credential_response_encryption`](/docs/issuance/credential-issuance/e2e-encryption#encryption-key-provisioning) property in the raw request payload to specify encryption details. ### **Analytic events** * OPENID_CREDENTIAL_START * OPENID_CREDENTIAL_SUCCESS * OPENID_CREDENTIAL_FAIL ### Request Body ### Responses #### 200 - Credential issued # Create an Authorization Code credential offer ## Endpoint ``` POST /v1/openid/offers ``` Full URL: `https://example.vii.au01.mattr.global/v1/openid/offers` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns an OID4VCI credential offer URI. See [https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-10.1](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-10.1) This offer can be used more than once, and can be shared with multiple users. Each user will authenticate independently during the credential issuance workflow, allowing the same offer URI to issue credentials with different user-specific data to multiple holders. ### **Analytic events** * OPENID_OFFER_CREATE_START * OPENID_OFFER_CREATE_SUCCESS * OPENID_OFFER_CREATE_FAIL ### Request Body ```json { "credentials": [ "707e920a-f342-443b-ae24-6946b7b5033e" ], "request_parameters": { "login_hint": "user@example.com", "prompt": "login" } } ``` ### Responses #### 200 - Credential offer URI created ```json { "uri": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fmyissuer.example.com%22%2C%22credentials%22%3A%5B%22707e920a-f342-443b-ae24-6946b7b5033e%22%5D%2C%22credential_configuration_ids%22%3A%5B%22707e920a-f342-443b-ae24-6946b7b5033e%22%5D%2C%22request_parameters%22%3A%7B%22login_hint%22%3A%22user%40example.com%22%2C%22prompt%22%3A%22login%22%7D%7D" } ``` # Create a Pre-Authorized Code credential offer ## Endpoint ``` POST /v1/openid/offers/pre-authorized ``` Full URL: `https://example.vii.au01.mattr.global/v1/openid/offers/pre-authorized` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Generate a new [OID4VCI Pre-Authorized Code](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-4.1) credential offer. This offer can only be used once. Once the offer is successfully claimed and the credential is issued, the pre-authorized code is consumed and the offer becomes invalid. The offer cannot be claimed again, even if the same user attempts to claim it. This is a security measure to prevent unauthorized credential duplication. If you need to issue another credential to the same user, you must generate a new credential offer. The Pre-authorized Code flow is only supported for the mDocs credential format. The total size of the request cannot exceed 500KB, including any claims. This is mostly relevant when including large claims such as images. When holders present credentials over Bluetooth Low Energy (BLE), keep payloads as small as practical, as large claims can degrade the transfer experience. Reserve larger payloads for remote presentation flows. ### **Analytic events** * OPENID_PRE_AUTHORIZED_OFFER_CREATE_START * OPENID_PRE_AUTHORIZED_OFFER_CREATE_SUCCESS * OPENID_PRE_AUTHORIZED_OFFER_CREATE_FAIL ### Request Body ```json { "credentials": [ "707e920a-f342-443b-ae24-6946b7b5033e" ], "transactionCodeConfiguration": { "inputMode": "numeric", "description": "Please enter the one-time code that was sent to you via email." }, "claims": { "givenName": "John", "familyName": "Doe", "email": "john.doe@example.com" }, "claimsToPersist": [ "userId" ], "expiresIn": { "minutes": 5 } } ``` ### Responses #### 200 - Credential offer created ```json { "uri": "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fexample.com%22%2C%22credentials%22%3A%5B%222edaf985-fcc2-4448-9c8e-a04c6c7351c2%22%5D%2C%22credential_configuration_ids%22%3A%5B%222edaf985-fcc2-4448-9c8e-a04c6c7351c2%22%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22stukD6lg9c9tQ3jUCa32wVi1HI%2BQIVsFK%2FQPvC2CHRs%3D%22%2C%22tx_code%22%3A%7B%22length%22%3A6%2C%22input_mode%22%3A%22numeric%22%2C%22description%22%3A%22Please%20provide%20the%20one-time%20code%20that%20was%20sent%20via%20e-mail%22%7D%7D%7D%7D", "expiresAt": "2025-05-01T00:01:00.000Z", "transactionCode": 493536 } ``` # Delete a Pre-authorized Code credential offer ## Endpoint ``` DELETE /v1/openid/offers/pre-authorized/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/openid/offers/pre-authorized/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Delete an [OID4VCI Pre-authorized Code](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-4.1) credential offer. ### **Analytic events** * OPENID_PRE_AUTHORIZED_OFFER_DELETE_START * OPENID_PRE_AUTHORIZED_OFFER_DELETE_SUCCESS * OPENID_PRE_AUTHORIZED_OFFER_DELETE_FAIL ### Responses #### 204 - Credential offer deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve OID4VCI issuer metadata ## Endpoint ``` GET /.well-known/openid-credential-issuer ``` Full URL: `https://example.vii.au01.mattr.global/.well-known/openid-credential-issuer` ### Authorization None required. ## Description Returns OID4VCI issuer metadata. This is the standard OID4VCI Well Known endpoint for your tenant. This endpoint is unprotected, public facing and can be deterministically found at the root of the tenant subdomain or alias by any party wishing to discover the OID4VCI capabilities. ### Responses #### 200 - OID4VCI credential issuer metadata retrieved ```json { "scopes_supported": [ "ldp_vc:ExampleCredential" ], "response_types_supported": [ "code" ], "response_modes_supported": [ "query" ], "grant_types_supported": [ "authorization_code" ], "code_challenge_methods_supported": [ "S256" ], "credential_configurations_supported": { "2cdb2c15-39a7-4556-abab-4515ce2d831b": { "format": "ldp_vc", "id": "2cdb2c15-39a7-4556-abab-4515ce2d831b", "scope": "ldp_vc:TestCredential", "credential_definition": { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://schema.org" ], "type": [ "VerifiableCredential", "TestCredential" ] }, "credential_signing_alg_values_supported": [ "Ed25519Signature2018", "BbsSignatureProof2022" ], "cryptographic_binding_methods_supported": [ "did:key" ], "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": [ "EdDSA" ] } }, "credential_metadata": { "display": [ { "name": "Test Credential", "logo": { "uri": "https://example.com/logo.png", "alt_text": "Example Logo" }, "locale": "en-US", "background_color": "#FFFFFF", "text_color": "#000000" } ], "claims": [ { "path": [ "credentialSubject", "firstName" ], "mandatory": true, "display": [ { "name": "First Name", "locale": "en-US" } ] } ] } }, "3dfe1c4a-5b6c-4e2f-9f3a-2b1c4d5e6f7g": { "format": "cwt_vc", "id": "3dfe1c4a-5b6c-4e2f-9f3a-2b1c4d5e6f7g", "scope": "cwt_vc:TestCredential", "types": [ "VerifiableCredential", "TestCredential" ], "cryptographic_binding_methods_supported": [], "credential_signing_alg_values_supported": [ -7 ], "credential_metadata": { "claims": [ { "path": [ "vc", "credentialSubject", "firstName" ], "mandatory": true, "display": [ { "name": "First Name", "locale": "en-US" } ] } ] } }, "b068c060-cc72-4758-9526-92d29edb821f": { "format": "cwt", "id": "b068c060-cc72-4758-9526-92d29edb821f", "scope": "cwt:TestCredential", "type": "TestCredential", "cryptographic_binding_methods_supported": [], "credential_signing_alg_values_supported": [ -7 ], "credential_metadata": { "claims": [ { "path": [ "firstName" ], "mandatory": true, "display": [ { "name": "First Name", "locale": "en-US" } ] } ] } }, "a1b2c3d4-e5f6-4789-abcd-ef0123456789": { "format": "mso_mdoc", "doctype": "org.iso.18013.5.1.mDL.T", "scope": "mso_mdoc:TestCredential", "id": "a1b2c3d4-e5f6-4789-abcd-ef0123456789", "cryptographic_binding_methods_supported": [ "mso" ], "credential_signing_alg_values_supported": [ -7 ], "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": [ "ES256" ] } }, "credential_metadata": { "claims": [ { "path": [ "org.iso.18013.5.1", "firstName" ], "mandatory": true, "display": [ { "name": "First Name", "locale": "en-US" } ] } ], "display": [ { "name": "Test Mobile Credential", "logo": { "uri": "https://example.com/logo.png", "alt_text": "Example Logo" }, "locale": "en-US", "background_color": "#FFFFFF", "text_color": "#000000" } ] } } }, "credential_response_encryption": { "alg_values_supported": [ "HPKE-7" ], "enc_values_supported": [ "A256GCM" ], "encryption_required": false }, "credential_request_encryption": { "jwks": { "keys": [ { "kty": "EC", "kid": "kid", "use": "enc", "crv": "P-256", "alg": "HPKE-7", "x": "YO4epjifD-KWeq1sL2tNmm36BhXnkJ0He-WqMYrp9Fk", "y": "Hekpm0zfK7C-YccH5iBjcIXgf6YdUvNUac_0At55Okk" } ] }, "enc_values_supported": [ "A256GCM" ], "encryption_required": false } } ``` # Retrieve authorization server metadata ## Endpoint ``` GET /.well-known/oauth-authorization-server ``` Full URL: `https://example.vii.au01.mattr.global/.well-known/oauth-authorization-server` ### Authorization None required. ## Description Returns the OAuth 2.0 Authorization Server Metadata for your tenant. This is the standard OAuth 2.0 Well-Known endpoint, providing public metadata that describes the tenant’s OAuth 2.0 configuration and capabilities. https://www.rfc-editor.org/rfc/rfc8414.html This endpoint is intended for public consumption, and as such does not require authentication. ### Responses #### 200 - OAuth authorization server metadata ```json { "scopes_supported": [ "ldp_vc:ExampleCredential", "ldp_vc:CourseCredential", "cwt:CourseCredential", "cwt_vc:CourseCredential", "mso_mdoc:org.iso.18013.5.1.mDL" ], "response_types_supported": [ "code" ], "response_modes_supported": [ "query", "fragment" ], "grant_types_supported": [ "authorization_code" ], "token_endpoint_auth_methods_supported": [ "client_secret_basic", "none" ], "code_challenge_methods_supported": [ "S256" ], "dpop_signing_alg_values_supported": [ "ES256" ] } ``` # Retrieve all claims sources ## Endpoint ``` GET /v1/claim-sources ``` Full URL: `https://example.vii.au01.mattr.global/v1/claim-sources` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a list of all claims sources configured on your tenant. ### **Analytic event** * CLAIM_SOURCE_RETRIEVE_LIST_START * CLAIM_SOURCE_RETRIEVE_LIST_SUCCESS * CLAIM_SOURCE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number Range size of returned entries, default 100 - `cursor`: string Starting point for the range of entries ### Responses #### 200 - Claims sources retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "57fa09e2-82f3-4d3d-9eca-d0253e84a4e6", "name": "My claims from example.com", "url": "https://example.com" } ] } ``` #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Configure a claims source ## Endpoint ``` POST /v1/claim-sources ``` Full URL: `https://example.vii.au01.mattr.global/v1/claim-sources` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Configures a new claims source for your tenant. When issuing a new credential, MATTR VII will make either a GET or a POST request to the claims source using the configured request parameters and fetch available data. This fetched data can then be included in the issued credential. ### **Analytic event** * CLAIM_SOURCE_CREATE_START * CLAIM_SOURCE_CREATE_SUCCESS * CLAIM_SOURCE_CREATE_FAIL ### Request Body The claim source payload ```json { "name": "My claims from example.com", "url": "https://example.com" } ``` ### Responses #### 201 - Claims source configured ```json { "id": "57fa09e2-82f3-4d3d-9eca-d0253e84a4e6", "name": "My claims from example.com", "url": "https://example.com" } ``` #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a claims source ## Endpoint ``` GET /v1/claim-sources/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/claim-sources/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an existing claims source by providing its `claimSourceID`. ### **Analytic event** * CLAIM_SOURCE_RETRIEVE_START * CLAIM_SOURCE_RETRIEVE_SUCCESS * CLAIM_SOURCE_RETRIEVE_FAIL ### Responses #### 200 - Claims source retrieved ```json { "id": "57fa09e2-82f3-4d3d-9eca-d0253e84a4e6", "name": "My claims from example.com", "url": "https://example.com" } ``` #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Claims source not found ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a claims source ## Endpoint ``` DELETE /v1/claim-sources/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/claim-sources/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes an existing claims source by providing its `claimSourceID`. ### **Analytic event** * CLAIM_SOURCE_DELETE_START * CLAIM_SOURCE_DELETE_SUCCESS * CLAIM_SOURCE_DELETE_FAIL ### Responses #### 204 - Claims source deleted #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Claims source not found ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a claims source ## Endpoint ``` PUT /v1/claim-sources/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/claim-sources/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates an existing claim source by providing its `claimSourceID`. ### **Analytic event** * CLAIM_SOURCE_UPDATE_START * CLAIM_SOURCE_UPDATE_SUCCESS * CLAIM_SOURCE_UPDATE_FAIL ### Request Body The updated claims source payload ```json { "name": "My claims from example.com", "url": "https://example.com" } ``` ### Responses #### 200 - Claims source updated ```json { "id": "57fa09e2-82f3-4d3d-9eca-d0253e84a4e6", "name": "My claims from example.com", "url": "https://example.com" } ``` #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Claims source not found ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # List inboxes ## Endpoint ``` GET /v1/messaging/inboxes ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a list of all inboxes on the tenant. ### **Analytic events** * MESSAGING_INBOX_RETRIEVE_LIST_START * MESSAGING_INBOX_RETRIEVE_LIST_SUCCESS * MESSAGING_INBOX_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - A list of inboxes ```json { "data": [ { "id": "f04faabf-cea8-4f39-95b3-0ce357ac4d03", "name": "My_Inbox" } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` # Create an inbox ## Endpoint ``` POST /v1/messaging/inboxes ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Creates an inbox that can register DIDs and then hold messages sent to those DIDs service points. ### **Analytic events** * MESSAGING_INBOX_CREATE_START * MESSAGING_INBOX_CREATE_SUCCESS * MESSAGING_INBOX_CREATE_FAIL ### Request Body Inbox configuration ```json { "name": "My_Inbox" } ``` ### Responses #### 201 - Inbox created ```json { "id": "f04faabf-cea8-4f39-95b3-0ce357ac4d03", "name": "My_Inbox" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve an inbox ## Endpoint ``` GET /v1/messaging/inboxes/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves an inbox based on its ID. ### **Analytic events** * MESSAGING_INBOX_RETRIEVE_START * MESSAGING_INBOX_RETRIEVE_SUCCESS * MESSAGING_INBOX_RETRIEVE_FAIL ### Responses #### 200 - Inbox returned ```json { "id": "f04faabf-cea8-4f39-95b3-0ce357ac4d03", "name": "My_Inbox" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete an inbox ## Endpoint ``` DELETE /v1/messaging/inboxes/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes an inbox by providing its ID. ### **Analytic events** * MESSAGING_INBOX_DELETE_START * MESSAGING_INBOX_DELETE_SUCCESS * MESSAGING_INBOX_DELETE_FAIL ### Responses #### 204 - Inbox deleted #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update an inbox ## Endpoint ``` PUT /v1/messaging/inboxes/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Update the inbox configurations ### Request Body Updates an inbox name. ### **Analytic events** * MESSAGING_INBOX_UPDATE_START * MESSAGING_INBOX_UPDATE_SUCCESS * MESSAGING_INBOX_UPDATE_FAIL ### Responses #### 200 - Inbox updated ```json { "id": "f04faabf-cea8-4f39-95b3-0ce357ac4d03", "name": "My_Inbox" } ``` #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve inbox DIDs ## Endpoint ``` GET /v1/messaging/inboxes/{id}/dids ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes/{id}/dids` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves a list of all the DIDs registered with the requested inbox. ### **Analytic events** * MESSAGING_INBOX_DID_RETRIEVE_LIST_START * MESSAGING_INBOX_DID_RETRIEVE_LIST_SUCCESS * MESSAGING_INBOX_DID_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - A list of inbox DIDs ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ "did:key:did1", "did:key:did2", "did:key:did3" ] } ``` #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Register DID with an inbox ## Endpoint ``` POST /v1/messaging/inboxes/{id}/dids ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes/{id}/dids` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Register the provided DID to the requested inbox. DID registration with inboxes is currently limited to `did:key`' ### **Analytic events** * MESSAGING_INBOX_DID_REGISTER_START * MESSAGING_INBOX_DID_REGISTER_SUCCESS * MESSAGING_INBOX_DID_REGISTER_FAIL ### Request Body DID registration information ### Responses #### 201 - DID registered with inbox #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Unregister DID with an inbox ## Endpoint ``` DELETE /v1/messaging/inboxes/{id}/dids/{did} ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes/{id}/dids/{did}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Unregisters a DID from the requested inbox. ### **Analytic events** * MESSAGING_INBOX_DID_UNREGISTER_START * MESSAGING_INBOX_DID_UNREGISTER_SUCCESS * MESSAGING_INBOX_DID_UNREGISTER_FAIL ### Responses #### 204 - DID unregistered from inbox #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all messages ## Endpoint ``` GET /v1/messaging/inboxes/{id}/messages ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes/{id}/messages` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieving all the messages from an inbox ### **Analytic events** * MESSAGING_INBOX_MESSAGE_RETRIEVE_LIST_START * MESSAGING_INBOX_MESSAGE_RETRIEVE_LIST_SUCCESS * MESSAGING_INBOX_MESSAGE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - A list of inbox messages ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a message ## Endpoint ``` GET /v1/messaging/inboxes/{id}/messages/{messageid} ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes/{id}/messages/{messageid}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves a message from the requested inbox by providing its ID. ### **Analytic events** * MESSAGING_INBOX_MESSAGE_RETRIEVE_START * MESSAGING_INBOX_MESSAGE_RETRIEVE_SUCCESS * MESSAGING_INBOX_MESSAGE_RETRIEVE_FAIL ### Responses #### 200 - An inbox message #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a message ## Endpoint ``` DELETE /v1/messaging/inboxes/{id}/messages/{messageid} ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/inboxes/{id}/messages/{messageid}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes a message from the requested inbox by providing its ID. ### **Analytic events** * MESSAGING_INBOX_MESSAGE_DELETE_START * MESSAGING_INBOX_MESSAGE_DELETE_SUCCESS * MESSAGING_INBOX_MESSAGE_DELETE_FAIL ### Responses #### 204 - Message deleted #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Sign a message ## Endpoint ``` POST /v1/messaging/sign ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/sign` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Accepts a message payload and signs it with a JWS (JSON Web Signature) using the a specific key from the DID (Decentralized Identifier) provided in the request. ### **Analytic events** * MESSAGING_SIGN_START * MESSAGING_SIGN_SUCCESS * MESSAGING_SIGN_FAIL ### Request Body Sign message request ```json { "didUrl": "did:example:abcdefghijkl#key1", "payload": { "msg": "this is a message" } } ``` ### Responses #### 200 - Message signed ```json "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa21mazNtMldIQlVxVm94SlZ3R1NQejVrYmFKNnpBMXRwN1JRWUJiUUdtczNoI3o2TWttZmszbTJXSEJVcVZveEpWd0dTUHo1a2JhSjZ6QTF0cDdSUVlCYlFHbXMzaCJ9.eyJtc2ciOiJUaGlzIGlzIGEgcGF5bG9hZCJ9.5E9qEmmSOMHLABAr4A9VzuNKFaO4EDo2GSCMoxQm9zsE7eCmEEuaAxtNhOUdd-Wvj64vqBBVl84XB1Yg7X9wBg" ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Verify a message ## Endpoint ``` POST /v1/messaging/verify ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/verify` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Verifies the signature of a provided JWS (JSON Web Signature), validating that the payload has not been tampered with and verifying that the kid in the JWS header is the same as the `iss` value in the Request Object. One use case for verifying a JWS with a DID is when the Mobile Wallet App sends a Request Object to an OpenID Provider as part of the Authorization Code Flow (as per https://openid.net/specs/openid-connect-core-1_0-final.html#RequestObject). The Request Object is wrapped in a JWS with a signature that is generated from the Subject DID on the mobile app. Therefore verifying the JWS proves that the mobile app has access to the private key of the Subject DID. ### **Analytic events** * MESSAGING_VERIFY_START * MESSAGING_VERIFY_SUCCESS * MESSAGING_VERIFY_FAIL ### Request Body Provide the JWS to verify ```json { "jws": "EXAMPLE_JWS_TOKEN_eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" } ``` ### Responses #### 200 - Verification successful ```json { "didUrl": "did:web:organization.com#2vcj3MjR4d", "did": "did:web:organization.com", "verified": true } ``` #### 400 - Invalid JWS ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Encrypt a message ## Endpoint ``` POST /v1/messaging/encrypt ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/encrypt` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Encrypts the provided payload using into a JWM (JSON Web Message) format. ### **Analytic events** * MESSAGING_ENCRYPT_START * MESSAGING_ENCRYPT_SUCCESS * MESSAGING_ENCRYPT_FAIL ### Request Body Encryption parameters ### Responses #### 200 - Message encrypted #### 400 - Bad Request ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Decrypt a message ## Endpoint ``` POST /v1/messaging/decrypt ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/decrypt` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Decrypts a provided message where the tenant manages the keys for the defined `recipientDidUrl`. ### **Analytic events** * MESSAGING_DECRYPT_START * MESSAGING_DECRYPT_SUCCESS * MESSAGING_DECRYPT_FAIL ### Request Body Decryption parameters ### Responses #### 200 - Message Decrypted ```json { "senderDidUrl": "did:web:organization.com#2vcj3MjR4d", "recipientDidUrl": "did:key:z6MkgmEkNM32vyFeMXcQA7AfQDznu47qHCZpy2AYH2Dtdu1d" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Send a message ## Endpoint ``` POST /v1/messaging/send ``` Full URL: `https://example.vii.au01.mattr.global/v1/messaging/send` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Sends an encrypted JWM (JSON Web Messaging) format message to a service endpoint defined in a public DID document. ### **Analytic events** * MESSAGING_SEND_START * MESSAGING_SEND_SUCCESS * MESSAGING_SEND_FAIL ### Request Body ### Responses #### 200 - Message sent #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Search users ## Endpoint ``` POST /v1/users/search ``` Full URL: `https://example.vii.au01.mattr.global/v1/users/search` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a list of users from the tenant that match all the provided search criteria (all criteria are optional). ### **Analytic events** * USER_SEARCH_START * USER_SEARCH_SUCCESS * USER_SEARCH_FAIL ### Request Body The search criteria ```json { "claims": { "externalUserId": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba" }, "authenticationProvider": { "providerId": "41458e5a-9092-40b7-9a26-d4eb43c5792f", "subjectId": "example-university-oauth2|123456789" }, "limit": 100, "cursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1h" } ``` ### Responses #### 200 - Users retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba", "claims": { "externalUserId": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba" }, "authenticationProvider": { "providerId": "41458e5a-9092-40b7-9a26-d4eb43c5792f", "url": "https://example-university.au.auth0.com", "subjectId": "example-university-oauth2|123456789" } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all users ## Endpoint ``` GET /v1/users ``` Full URL: `https://example.vii.au01.mattr.global/v1/users` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a list of all users on your tenant. ### **Analytic events** * USER_RETRIEVE_LIST_START * USER_RETRIEVE_LIST_SUCCESS * USER_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Users retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba", "claims": { "externalUserId": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba" }, "authenticationProvider": { "providerId": "41458e5a-9092-40b7-9a26-d4eb43c5792f", "url": "https://example-university.au.auth0.com", "subjectId": "example-university-oauth2|123456789" } } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a User ## Endpoint ``` POST /v1/users ``` Full URL: `https://example.vii.au01.mattr.global/v1/users` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Create a user. * USER_CREATE_START * USER_CREATE_SUCCESS * USER_CREATE_FAIL ### Request Body Create a User ```json { "claims": { "externalUserId": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba" }, "authenticationProvider": { "providerId": "41458e5a-9092-40b7-9a26-d4eb43c5792f", "subjectId": "example-university-oauth2|123456789" } } ``` ### Responses #### 201 - User created ```json { "id": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba", "claims": { "externalUserId": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba" }, "authenticationProvider": { "providerId": "41458e5a-9092-40b7-9a26-d4eb43c5792f", "url": "https://example-university.au.auth0.com", "subjectId": "example-university-oauth2|123456789" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a user ## Endpoint ``` GET /v1/users/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/users/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieve an existing user by providing its ID. * USER_RETRIEVE_START * USER_RETRIEVE_SUCCESS * USER_RETRIEVE_FAIL ### Responses #### 200 - User retrieved ```json { "id": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba", "claims": { "externalUserId": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba" }, "authenticationProvider": { "providerId": "41458e5a-9092-40b7-9a26-d4eb43c5792f", "url": "https://example-university.au.auth0.com", "subjectId": "example-university-oauth2|123456789" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a user ## Endpoint ``` DELETE /v1/users/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/users/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes a user and removes all data related to them by providing the user ID. Deleting a user is irreversible. All of the user's data is removed, and any credential previously issued to them is no longer valid. ### **Analytic events** * USER_DELETE_START * USER_DELETE_SUCCESS * USER_DELETE_FAIL ### Responses #### 204 - User deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a User ## Endpoint ``` PUT /v1/users/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/users/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates an existing user by providing its ID. * USER_UPDATE_START * USER_UPDATE_SUCCESS * USER_UPDATE_FAIL ### Request Body Update a User ```json { "claims": { "externalUserId": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba" }, "authenticationProvider": { "providerId": "41458e5a-9092-40b7-9a26-d4eb43c5792f", "subjectId": "example-university-oauth2|123456789" } } ``` ### Responses #### 200 - User updated ```json { "id": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba", "claims": { "externalUserId": "0c3fad74-a8df-4a2d-8e75-f2d356b413ba" }, "authenticationProvider": { "providerId": "41458e5a-9092-40b7-9a26-d4eb43c5792f", "url": "https://example-university.au.auth0.com", "subjectId": "example-university-oauth2|123456789" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all user credentials data ## Endpoint ``` GET /v1/users/{userId}/credentials ``` Full URL: `https://example.vii.au01.mattr.global/v1/users/{userId}/credentials` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns metadata for all the credentials issued to the provided `userId`. ### **Analytic events** * USER_CREDENTIAL_RETRIEVE_LIST_START * USER_CREDENTIAL_RETRIEVE_LIST_SUCCESS * USER_CREDENTIAL_RETRIEVE_LIST_FAIL ### Responses #### 200 - User credentials retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Authentication Providers ## Endpoint ``` GET /v1/users/authentication-providers ``` Full URL: `https://example.vii.au01.mattr.global/v1/users/authentication-providers` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Returns a list of all Authentication Providers on your tenant. ### **Analytic events** * USER_AUTHENTICATION_PROVIDER_RETRIEVE_LIST_START * USER_AUTHENTICATION_PROVIDER_RETRIEVE_LIST_SUCCESS * USER_AUTHENTICATION_PROVIDER_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Authentication Providers retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM", "data": [ { "id": "983c0a86-204f-4431-9371-f5a22e506599", "redirectUrl": "https://tenant.vii.mattr.global/v1/oauth/authentication/callback", "scope": [ "openid", "profile", "email", "address", "phone" ], "clientId": "vJ0SCKchr4XjC0xHNE8DkH6Pmlg2lkCN", "tokenEndpointAuthMethod": "client_secret_post", "staticRequestParameters": { "prompt": "login", "max_age": 10000 }, "forwardedRequestParameters": [ "login_hint" ], "claimsToPersist": [ "userId" ], "clientSecret": "***********************************************************6-OjH" } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Configure an Authentication Provider ## Endpoint ``` POST /v1/users/authentication-providers ``` Full URL: `https://example.vii.au01.mattr.global/v1/users/authentication-providers` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Configures an Authentication Provider on the tenant. An authentication or identity provider (IdP) is a platform that is typically used to store and manage user accounts on behalf of an organization or a service provider. MATTR VII uses the authentication provider to authenticate end users before issuing them credentials. Only one authentication provider can be configured on a tenant. The `/.well-known/openid-configuration` endpoint of the Authentication Provider must contain values for the `authorization_endpoint`, `token_endpoint` and `scopes_supported`. ### **Analytic events** * USER_AUTHENTICATION_PROVIDER_CREATE_START * USER_AUTHENTICATION_PROVIDER_CREATE_SUCCESS * USER_AUTHENTICATION_PROVIDER_CREATE_FAIL ### Request Body The Authentication Provider payload ```json { "scope": [ "openid", "profile", "email", "address", "phone" ], "clientId": "vJ0SCKchr4XjC0xHNE8DkH6Pmlg2lkCN", "tokenEndpointAuthMethod": "client_secret_post", "staticRequestParameters": { "prompt": "login", "max_age": 10000 }, "forwardedRequestParameters": [ "login_hint" ], "claimsToPersist": [ "userId" ], "clientSecret": "QNwfa4Yi4Im9zy1u_15n7SzWKt-9G5cdH0r1bONRpUPfN-UIRaaXv_90z8V6-OjH", "url": "https://example-university.au.auth0.com" } ``` ### Responses #### 201 - Authentication Provider configured ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "redirectUrl": "https://tenant.vii.mattr.global/v1/oauth/authentication/callback", "scope": [ "openid", "profile", "email", "address", "phone" ], "clientId": "vJ0SCKchr4XjC0xHNE8DkH6Pmlg2lkCN", "tokenEndpointAuthMethod": "client_secret_post", "staticRequestParameters": { "prompt": "login", "max_age": 10000 }, "forwardedRequestParameters": [ "login_hint" ], "claimsToPersist": [ "userId" ], "clientSecret": "***********************************************************6-OjH" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve an Authentication Provider ## Endpoint ``` GET /v1/users/authentication-providers/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/users/authentication-providers/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieve an existing Authentication Provider by providing its ID. ### **Analytic events** * USER_AUTHENTICATION_PROVIDER_RETRIEVE_START * USER_AUTHENTICATION_PROVIDER_RETRIEVE_SUCCESS * USER_AUTHENTICATION_PROVIDER_RETRIEVE_FAIL ### Responses #### 200 - Authentication Provider retrieved ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "redirectUrl": "https://tenant.vii.mattr.global/v1/oauth/authentication/callback", "scope": [ "openid", "profile", "email", "address", "phone" ], "clientId": "vJ0SCKchr4XjC0xHNE8DkH6Pmlg2lkCN", "tokenEndpointAuthMethod": "client_secret_post", "staticRequestParameters": { "prompt": "login", "max_age": 10000 }, "forwardedRequestParameters": [ "login_hint" ], "claimsToPersist": [ "userId" ], "clientSecret": "***********************************************************6-OjH" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete an Authentication Provider ## Endpoint ``` DELETE /v1/users/authentication-providers/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/users/authentication-providers/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes an existing Authentication Provider by providing its ID. ### **Analytic events** * USER_AUTHENTICATION_PROVIDER_DELETE_START * USER_AUTHENTICATION_PROVIDER_DELETE_SUCCESS * USER_AUTHENTICATION_PROVIDER_DELETE_FAIL ### Responses #### 204 - Authentication Provider deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update an Authentication Provider ## Endpoint ``` PUT /v1/users/authentication-providers/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/users/authentication-providers/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates an existing Authentication Provider by providing its ID. ### **Analytic events** * USER_AUTHENTICATION_PROVIDER_UPDATE_START * USER_AUTHENTICATION_PROVIDER_UPDATE_SUCCESS * USER_AUTHENTICATION_PROVIDER_UPDATE_FAIL ### Request Body Update an Authentication Provider ```json { "scope": [ "openid", "profile", "email", "address", "phone" ], "clientId": "vJ0SCKchr4XjC0xHNE8DkH6Pmlg2lkCN", "tokenEndpointAuthMethod": "client_secret_post", "staticRequestParameters": { "prompt": "login", "max_age": 10000 }, "forwardedRequestParameters": [ "login_hint" ], "claimsToPersist": [ "userId" ], "clientSecret": "QNwfa4Yi4Im9zy1u_15n7SzWKt-9G5cdH0r1bONRpUPfN-UIRaaXv_90z8V6-OjH" } ``` ### Responses #### 200 - Authentication Provider updated ```json { "id": "983c0a86-204f-4431-9371-f5a22e506599", "redirectUrl": "https://tenant.vii.mattr.global/v1/oauth/authentication/callback", "scope": [ "openid", "profile", "email", "address", "phone" ], "clientId": "vJ0SCKchr4XjC0xHNE8DkH6Pmlg2lkCN", "tokenEndpointAuthMethod": "client_secret_post", "staticRequestParameters": { "prompt": "login", "max_age": 10000 }, "forwardedRequestParameters": [ "login_hint" ], "claimsToPersist": [ "userId" ], "clientSecret": "***********************************************************6-OjH" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve Interaction Hook ## Endpoint ``` GET /v1/openid/configuration ``` Full URL: `https://example.vii.au01.mattr.global/v1/openid/configuration` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves the Interaction Hook configuration from your tenant. ### **Analytic events** * CREDENTIAL_PROVIDER_OPENID_CONFIGURATION_RETRIEVE_START * CREDENTIAL_PROVIDER_OPENID_CONFIGURATION_RETRIEVE_SUCCESS * CREDENTIAL_PROVIDER_OPENID_CONFIGURATION_RETRIEVE_FAIL ### Responses #### 200 - Interaction Hook configuration retrieved ```json { "interactionHook": { "url": "https://example-university.com/callback", "claims": [ "first_name", "last_name", "email" ], "sessionTimeoutInSec": 1200, "disabled": false, "secret": "dGtUrijBOT6UUJ8JO4kAFyGfhahDlVVeIk/sPbWTa7c=" } } ``` # Configure Interaction Hook ## Endpoint ``` PUT /v1/openid/configuration ``` Full URL: `https://example.vii.au01.mattr.global/v1/openid/configuration` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Configure the Interaction Hook for the OID4VCI protocol on your tenant. Many credential issuance journeys require the issuer to perform custom interactions with the user. This could be gathering more information, performing additional authentication steps (E.g, 2FA, MFA or biometric checks) or communicating the terms of service. To facilitate this requirement, you can configure MATTR VII to invoke an interaction hook which will redirect the user to a custom component during the credential issuance journey. This redirect happens **after** the user is authenticated with your configured identity provider but **before** the credential is issued to the user. Upon successful completion of the interaction hook, your custom component will redirect the user back to their digital wallet to complete the credential issuance flow. Your interaction hook component can be either a web or native application.  We recommend using a web interface because it's more compatible with most scenarios. You can only configure one interaction hook on your MATTR VII tenant. If you require several custom interactions as part of the credential issuance workflow, they should all be linked into a single interaction hook component. ### **Analytic events** * CREDENTIAL_PROVIDER_OPENID_CONFIGURATION_UPDATE_START * CREDENTIAL_PROVIDER_OPENID_CONFIGURATION_UPDATE_SUCCESS * CREDENTIAL_PROVIDER_OPENID_CONFIGURATION_UPDATE_FAIL ### Request Body The Interaction Hook configuration payload ```json { "interactionHook": { "url": "https://example-university.com/callback", "claims": [ "first_name", "last_name", "email" ], "sessionTimeoutInSec": 1200, "disabled": false } } ``` ### Responses #### 200 - Interaction Hook configured ```json { "interactionHook": { "url": "https://example-university.com/callback", "claims": [ "first_name", "last_name", "email" ], "sessionTimeoutInSec": 1200, "disabled": false, "secret": "dGtUrijBOT6UUJ8JO4kAFyGfhahDlVVeIk/sPbWTa7c=" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve events ## Endpoint ``` GET /v1/events ``` Full URL: `https://example.vii.au01.mattr.global/v1/events` ### Authorization Bearer token required. Required roles: admin, auditor, managed-issuer ## Description Returns a list of matching events from the tenant's event database. The `categories` and `types` parameters filter based on an **OR** logic, whilst all other parameters use an "AND" logic. For example `(categories OR types) AND requestIds AND dateFrom`. Refer to the [Events registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) for an inclusive list of events categories and types. ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. - `ids`: array Query by event IDs. These can be retrieved from event details. - `requestIds`: array Query by request IDs. These can be retrieved from event details. The response will include all the individual events that are part of the queried request. - `categories`: array Query by event categories. Uses an **OR** operation with `types`. Every **category** includes several event **types**. Each API endpoint details the event types it generates under the **Analytic events** heading. Refer to the [Events registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) for an inclusive list. - `types`: array Query by event types. Uses an **OR** operation with `categories`. Every **category** includes several event **types**. Each API endpoint details the events it generates under the **Analytic events** heading. Refer to the [Events registry](https://api-reference-sdk.mattr.global/event-registry/latest/index.html) for an inclusive list. - `dateFrom`: string Query by event start date and time (inclusive), in ISO-8601 format. - `dateTo`: string Query by event end date and time (inclusive), in ISO-8601 format. - `managementUserIds` Filter events by management user IDs. You can obtain these IDs from the event details. The response will include all individual events associated with the specified management user IDs. Special filtering values: - `none`: Returns events that are not assigned to any management user IDs. - `*`: Returns events that are assigned to any management user IDs. - `clientIds` Filter events by client IDs. You can obtain these IDs from the event details. The response will include all individual events associated with the specified client IDs. Special filtering values: - `none`: Returns events that are not assigned to any client IDs. - `*`: Returns events that are assigned to any client IDs. ### Responses #### 200 - A list of events ```json { "data": [ { "id": "0c099611-19c4-4f29-8724-6b9e5ba1ef7c", "type": "CREDENTIAL_COMPACT_SIGN_START", "timestamp": "2023-06-01T02:45:44.087Z", "category": "credential-compact", "requestId": "4SO6JZz3sPYLjOQvxIVHr5", "requestIp": "192.0.2.1", "managementUserId": "ea691ed4-90ff-4be2-bd85-f2c74efa72c3", "clientId": "54rp8Z8yGnlva19mThj7tJzNXFSyXrCf" } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 500 - Internal Server Error. An unexpected error occurred. # Create credential report ## Endpoint ``` POST /v1/events/credential-reports ``` Full URL: `https://example.vii.au01.mattr.global/v1/events/credential-reports` ### Authorization Bearer token required. Required roles: admin, issuer, auditor, managed-issuer ## Description Creates a report of credential lifecycle operations (issuance and status changes). Only CWT and mDoc credentials are included in the report. ### Request Body The credential report parameters ```json { "timezoneOffset": "+02:00", "startDate": "2026-02-01", "endDate": "2026-02-28", "profiles": [ "cwt", "mdoc" ], "limit": 100, "cursor": "b2Zmc2V0PTM=" } ``` ### Responses #### 200 - Successfully generated credential report ```json { "data": [ { "date": "2026-02-25", "operation": "issued", "profile": "mdoc", "credentialType": "org.iso.18013.5.1.mDL", "count": 5 } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjYtMDItMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve all ecosystems ## Endpoint ``` GET /v1/ecosystems ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves a list of ecosystems. ### **Analytic events** * ECOSYSTEM_RETRIEVE_LIST_START * ECOSYSTEM_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Ecosystems retrieved ```json { "data": [ { "id": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Ecosystem" } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create ecosystem ## Endpoint ``` POST /v1/ecosystems ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Creates an ecosystem. ### **Analytic events** * ECOSYSTEM_CREATE_START * ECOSYSTEM_CREATE_SUCCESS * ECOSYSTEM_CREATE_FAIL ### Request Body ```json { "name": "My Ecosystem" } ``` ### Responses #### 201 - Ecosystem created ```json { "id": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Ecosystem" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Sync ecosystem ## Endpoint ``` POST /v1/ecosystems/sync ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/sync` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Synchronizes all configured consumptions and integrations by retrieving the most recent trust information. **Analytic events** * ECOSYSTEM_TENANT_SYNC_ALL_START * ECOSYSTEM_TENANT_SYNC_ALL_SUCCESS * ECOSYSTEM_TENANT_SYNC_ALL_FAIL ### Responses #### 202 - Ecosystem sync request accepted. ```json { "tenantConfiguration": { "ecosystems": [ { "url": "https://example.vii.au01.mattr.global/v1/ecosystems/489755c9-1d74-4f59-a127-db7105667bfe" } ] } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve ecosystem ## Endpoint ``` GET /v1/ecosystems/{ecosystemId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves an ecosystem by its ID. ### **Analytic events** * ECOSYSTEM_RETRIEVE_START * ECOSYSTEM_RETRIEVE_SUCCESS * ECOSYSTEM_RETRIEVE_FAIL ### Responses #### 200 - Ecosystem retrieved ```json { "id": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Ecosystem" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete ecosystem ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Deletes an ecosystem by its ID. ### **Analytic events** * ECOSYSTEM_DELETE_START * ECOSYSTEM_DELETE_SUCCESS * ECOSYSTEM_DELETE_FAIL ### Responses #### 204 - Ecosystem deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Update ecosystem ## Endpoint ``` PUT /v1/ecosystems/{ecosystemId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Updates an ecosystem by its ID. ### **Analytic events** * ECOSYSTEM_UPDATE_START * ECOSYSTEM_UPDATE_SUCCESS * ECOSYSTEM_UPDATE_FAIL ### Request Body ```json { "name": "My Ecosystem" } ``` ### Responses #### 200 - Ecosystem updated ```json { "id": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Ecosystem" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve ecosystem configuration ## Endpoint ``` GET /v1/config/ecosystems ``` Full URL: `https://example.vii.au01.mattr.global/v1/config/ecosystems` ### Authorization Bearer token required. Required roles: admin, dts-consumer ## Description Retrieves the tenant's ecosystem configuration. ### **Analytic events** * ECOSYSTEM_CONFIG_RETRIEVE_START * ECOSYSTEM_CONFIG_RETRIEVE_SUCCESS * ECOSYSTEM_CONFIG_RETRIEVE_FAIL ### Responses #### 200 - Ecosystem configuration retrieved ```json { "ecosystems": [ { "url": "https://example.vii.au01.mattr.global/v1/ecosystems/cdd42cec-e961-447c-9083-1312ee316053" } ], "isIssuanceRestricted": false, "isVerificationRestricted": false } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Create ecosystem configuration ## Endpoint ``` POST /v1/config/ecosystems ``` Full URL: `https://example.vii.au01.mattr.global/v1/config/ecosystems` ### Authorization Bearer token required. Required roles: admin, dts-consumer ## Description Creates an ecosystem configuration for the tenant. ### **Analytic events** * ECOSYSTEM_CONFIG_CREATE_START * ECOSYSTEM_CONFIG_CREATE_SUCCESS * ECOSYSTEM_CONFIG_CREATE_FAIL ### Request Body ```json { "ecosystems": [ { "url": "https://example.vii.au01.mattr.global/v1/ecosystems/cdd42cec-e961-447c-9083-1312ee316053" } ], "isIssuanceRestricted": false, "isVerificationRestricted": false } ``` ### Responses #### 201 - Ecosystem configuration created ```json { "ecosystems": [ { "url": "https://example.vii.au01.mattr.global/v1/ecosystems/cdd42cec-e961-447c-9083-1312ee316053" } ], "isIssuanceRestricted": false, "isVerificationRestricted": false } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Delete ecosystem configuration ## Endpoint ``` DELETE /v1/config/ecosystems ``` Full URL: `https://example.vii.au01.mattr.global/v1/config/ecosystems` ### Authorization Bearer token required. Required roles: admin, dts-consumer ## Description Deletes the tenant's ecosystem configuration. ### **Analytic events** * ECOSYSTEM_CONFIG_DELETE_START * ECOSYSTEM_CONFIG_DELETE_SUCCESS * ECOSYSTEM_CONFIG_DELETE_FAIL ### Responses #### 204 - Ecosystem configuration deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Update ecosystem configuration ## Endpoint ``` PUT /v1/config/ecosystems ``` Full URL: `https://example.vii.au01.mattr.global/v1/config/ecosystems` ### Authorization Bearer token required. Required roles: admin, dts-consumer ## Description Updates the tenant's ecosystem configuration. ### **Analytic events** * ECOSYSTEM_CONFIG_UPDATE_START * ECOSYSTEM_CONFIG_UPDATE_SUCCESS * ECOSYSTEM_CONFIG_UPDATE_FAIL ### Request Body ```json { "ecosystems": [ { "url": "https://example.vii.au01.mattr.global/v1/ecosystems/cdd42cec-e961-447c-9083-1312ee316053" } ], "isIssuanceRestricted": false, "isVerificationRestricted": false } ``` ### Responses #### 200 - Ecosystem configuration updated ```json { "ecosystems": [ { "url": "https://example.vii.au01.mattr.global/v1/ecosystems/cdd42cec-e961-447c-9083-1312ee316053" } ], "isIssuanceRestricted": false, "isVerificationRestricted": false } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Publish policy ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/policies ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/policies` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Publish a new version of the ecosystem policy. Only active and currently valid participants and IACA certificates are included in the policy. ### **Analytic events** * ECOSYSTEM_POLICY_CREATE_START * ECOSYSTEM_POLICY_CREATE_SUCCESS * ECOSYSTEM_POLICY_CREATE_FAIL ### Responses #### 201 - Ecosystem policy published ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "policyPublishedAt": "2024-10-22T00:00:00Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve latest ecosystem policy ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/policies/public/latest ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/policies/public/latest` ### Authorization None required. ## Description Retrieves the latest ecosystem policy by providing the ecosystem ID. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * ECOSYSTEM_POLICY_RETRIEVE_LATEST_START * ECOSYSTEM_POLICY_RETRIEVE_LATEST_SUCCESS * ECOSYSTEM_POLICY_RETRIEVE_LATEST_FAIL ### Responses #### 200 - Latest ecosystem policy retrieved ```json { "policyModifiedAt": "2023-10-17T00:00:00Z", "policyPublishedAt": "2024-10-22T00:00:00Z", "credentials": { "599bf148-d711-405a-a20b-9c8a87ac8850": { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "type": "DriverLicense", "name": "Driver's License" } }, "participants": { "a24e391a-c27f-4b6e-9805-1ee7e03f3c58": { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Participant", "identifiers": { "web-semantic": "did:web:example.com", "compact-semantic": "did:web:example.com", "compact": "did:web:example.com", "mobile": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "docTypes": [ "org.iso.18013.5.1" ] } ] }, "isIssuer": false, "isVerifier": false, "isIssuerConstrained": true, "isVerifierConstrained": true, "status": "Active", "country": "US", "stateOrProvince": "US-AL", "organizationAddress": "1234 Main St, City, State, 12345", "organizationPhoneNumber": "012-3456789", "issuerAllowedCredentials": [ "e0a07846-44e1-41a4-b704-1ccf6eb1a5af", "25fa6ffc-bf6e-417c-865c-96fcf1d7d1a3" ], "verifierAllowedCredentials": [ "4e25a240-76bb-4e9f-9f93-b93be287922b", "daca4a43-3ff9-4ecb-93fe-d9104e36bf74" ] } } } ``` #### 304 - Not Modified. The resource has not been modified since the last request. #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve ecosystem policy ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/policy ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/policy` ### Authorization None required. ## Description Retrieves an ecosystem policy by providing the ecosystem ID. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * ECOSYSTEM_POLICY_RETRIEVE_LATEST_START * ECOSYSTEM_POLICY_RETRIEVE_LATEST_SUCCESS * ECOSYSTEM_POLICY_RETRIEVE_LATEST_FAIL ### Responses #### 200 - Ecosystem policy retrieved ```json { "policyModifiedAt": "2023-10-17T00:00:00Z", "policyPublishedAt": "2024-10-22T00:00:00Z", "credentials": { "599bf148-d711-405a-a20b-9c8a87ac8850": { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "type": "DriverLicense", "name": "Driver's License" } }, "participants": { "a24e391a-c27f-4b6e-9805-1ee7e03f3c58": { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Participant", "identifiers": { "web-semantic": "did:web:example.com", "compact-semantic": "did:web:example.com", "compact": "did:web:example.com", "mobile": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "docTypes": [ "org.iso.18013.5.1" ] } ] }, "isIssuer": false, "isVerifier": false, "isIssuerConstrained": true, "isVerifierConstrained": true, "status": "Active", "country": "US", "stateOrProvince": "US-AL", "organizationAddress": "1234 Main St, City, State, 12345", "organizationPhoneNumber": "012-3456789", "issuerAllowedCredentials": [ "e0a07846-44e1-41a4-b704-1ccf6eb1a5af", "25fa6ffc-bf6e-417c-865c-96fcf1d7d1a3" ], "verifierAllowedCredentials": [ "4e25a240-76bb-4e9f-9f93-b93be287922b", "daca4a43-3ff9-4ecb-93fe-d9104e36bf74" ] } } } ``` #### 304 - Not Modified. The resource has not been modified since the last request. #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve ecosystem policy preview ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/preview ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/preview` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves an ecosystem policy preview by providing the Ecosystem's ID. A policy preview includes all the participants and credential types created in the ecosystem, excluding any participants with expired or inactive IACAs. ### **Analytic events** * ECOSYSTEM_POLICY_PREVIEW_RETRIEVE_START * ECOSYSTEM_POLICY_PREVIEW_RETRIEVE_SUCCESS * ECOSYSTEM_POLICY_PREVIEW_RETRIEVE_FAIL ### Responses #### 200 - Ecosystem policy preview retrieved ```json { "policyModifiedAt": "2023-10-17T00:00:00Z", "credentials": { "599bf148-d711-405a-a20b-9c8a87ac8850": { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "type": "DriverLicense", "name": "Driver's License" } }, "participants": { "a24e391a-c27f-4b6e-9805-1ee7e03f3c58": { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Participant", "identifiers": { "web-semantic": "did:web:example.com", "compact-semantic": "did:web:example.com", "compact": "did:web:example.com", "mobile": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Active", "docTypes": [ "org.iso.18013.5.1" ], "validationResult": { "validated": true } } ] }, "isIssuer": false, "isVerifier": false, "isIssuerConstrained": true, "isVerifierConstrained": true, "status": "Active", "country": "US", "stateOrProvince": "US-AL", "organizationAddress": "1234 Main St, City, State, 12345", "organizationPhoneNumber": "012-3456789", "issuerAllowedCredentials": [ "e0a07846-44e1-41a4-b704-1ccf6eb1a5af", "25fa6ffc-bf6e-417c-865c-96fcf1d7d1a3" ], "verifierAllowedCredentials": [ "4e25a240-76bb-4e9f-9f93-b93be287922b", "daca4a43-3ff9-4ecb-93fe-d9104e36bf74" ] } } } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Publish issuer policy ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/issuer-policies ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/issuer-policies` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Publish a new version of the ecosystem issuer policy. A participant is included only if it is active and has at least one issuer certificate that passes validation. Only the certificates that pass validation are included. ### **Analytic events** * ECOSYSTEM_ISSUER_POLICY_CREATE_START * ECOSYSTEM_ISSUER_POLICY_CREATE_SUCCESS * ECOSYSTEM_ISSUER_POLICY_CREATE_FAIL ### Responses #### 201 - Issuer policy published ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "publishedAt": "2024-10-22T00:00:00Z", "participants": [ { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "name": "My Participant", "issuerCertificates": [ { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", "docTypes": [ { "value": "org.iso.18013.5.1.mDL" } ] } ] } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve issuer policy preview ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/issuer-policies/preview ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/issuer-policies/preview` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves an ecosystem issuer policy preview by providing the ecosystem's ID. A policy preview includes all the participants and their issuer certificates created in the ecosystem, including those that fail validation and would be excluded from a published policy. The validation result is provided for each certificate. ### **Analytic events** * ECOSYSTEM_ISSUER_POLICY_PREVIEW_RETRIEVE_START * ECOSYSTEM_ISSUER_POLICY_PREVIEW_RETRIEVE_SUCCESS * ECOSYSTEM_ISSUER_POLICY_PREVIEW_RETRIEVE_FAIL ### Responses #### 200 - Issuer policy preview retrieved ```json { "publishedAt": "2024-10-22T00:00:00Z", "participants": [ { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "name": "My Participant", "status": "Active", "issuerCertificates": [ { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", "docTypes": [ { "value": "org.iso.18013.5.1.mDL" } ], "status": "Active" } ], "country": "NZ", "stateOrProvince": "Wellington" } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve latest issuer policy ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/issuer-policies/public/latest ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/issuer-policies/public/latest` ### Authorization None required. ## Description Retrieves the latest published ecosystem issuer policy by providing the ecosystem ID. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * ECOSYSTEM_ISSUER_POLICY_RETRIEVE_LATEST_START * ECOSYSTEM_ISSUER_POLICY_RETRIEVE_LATEST_SUCCESS * ECOSYSTEM_ISSUER_POLICY_RETRIEVE_LATEST_FAIL ### Responses #### 200 - Latest issuer policy retrieved ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "publishedAt": "2024-10-22T00:00:00Z", "participants": [ { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "name": "My Participant", "issuerCertificates": [ { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", "docTypes": [ { "value": "org.iso.18013.5.1.mDL" } ] } ] } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve issuer policy by ID ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/issuer-policies/public/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/issuer-policies/public/{id}` ### Authorization None required. ## Description Retrieves a specific published ecosystem issuer policy by providing the ecosystem ID and the issuer policy ID. The issuer policy ID is the `id` returned when a policy is published, and is also included in the response from the [Retrieve latest issuer policy](#operation/getLatestIssuerPolicy) endpoint. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * ECOSYSTEM_ISSUER_POLICY_RETRIEVE_START * ECOSYSTEM_ISSUER_POLICY_RETRIEVE_SUCCESS * ECOSYSTEM_ISSUER_POLICY_RETRIEVE_FAIL ### Responses #### 200 - Issuer policy retrieved ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "publishedAt": "2024-10-22T00:00:00Z", "participants": [ { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "name": "My Participant", "issuerCertificates": [ { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", "docTypes": [ { "value": "org.iso.18013.5.1.mDL" } ] } ] } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete issuer policy ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/issuer-policies/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/issuer-policies/{id}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Deletes a published ecosystem issuer policy by providing the ecosystem ID and the issuer policy ID. ### Responses #### 204 - Issuer policy deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Publish verifier policy ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/verifier-policies ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/verifier-policies` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Publish a new version of the ecosystem verifier policy. A participant is included only if it is active and has at least one verifier certificate that passes validation. Only the certificates that pass validation are included. ### **Analytic events** * ECOSYSTEM_VERIFIER_POLICY_CREATE_START * ECOSYSTEM_VERIFIER_POLICY_CREATE_SUCCESS * ECOSYSTEM_VERIFIER_POLICY_CREATE_FAIL ### Responses #### 201 - Verifier policy published ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "publishedAt": "2024-10-22T00:00:00Z", "participants": [ { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "name": "My Participant", "verifierCertificates": [ { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2" } ] } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve verifier policy preview ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/verifier-policies/preview ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/verifier-policies/preview` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves an ecosystem verifier policy preview by providing the ecosystem's ID. A policy preview includes all the participants and their verifier certificates created in the ecosystem, including those that fail validation and would be excluded from a published policy. The validation result is provided for each certificate. ### **Analytic events** * ECOSYSTEM_VERIFIER_POLICY_PREVIEW_RETRIEVE_START * ECOSYSTEM_VERIFIER_POLICY_PREVIEW_RETRIEVE_SUCCESS * ECOSYSTEM_VERIFIER_POLICY_PREVIEW_RETRIEVE_FAIL ### Responses #### 200 - Verifier policy preview retrieved ```json { "publishedAt": "2024-10-22T00:00:00Z", "participants": [ { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "name": "My Participant", "status": "Active", "verifierCertificates": [ { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2", "status": "Active" } ], "country": "NZ", "stateOrProvince": "Wellington" } ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve latest verifier policy ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/verifier-policies/public/latest ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/verifier-policies/public/latest` ### Authorization None required. ## Description Retrieves the latest published ecosystem verifier policy by providing the ecosystem ID. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * ECOSYSTEM_VERIFIER_POLICY_RETRIEVE_LATEST_START * ECOSYSTEM_VERIFIER_POLICY_RETRIEVE_LATEST_SUCCESS * ECOSYSTEM_VERIFIER_POLICY_RETRIEVE_LATEST_FAIL ### Responses #### 200 - Latest verifier policy retrieved ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "publishedAt": "2024-10-22T00:00:00Z", "participants": [ { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "name": "My Participant", "verifierCertificates": [ { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2" } ] } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve verifier policy by ID ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/verifier-policies/public/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/verifier-policies/public/{id}` ### Authorization None required. ## Description Retrieves a specific published ecosystem verifier policy by providing the ecosystem ID and the verifier policy ID. The verifier policy ID is the `id` returned when a policy is published, and is also included in the response from the [Retrieve latest verifier policy](#operation/getLatestVerifierPolicy) endpoint. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * ECOSYSTEM_VERIFIER_POLICY_RETRIEVE_START * ECOSYSTEM_VERIFIER_POLICY_RETRIEVE_SUCCESS * ECOSYSTEM_VERIFIER_POLICY_RETRIEVE_FAIL ### Responses #### 200 - Verifier policy retrieved ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "publishedAt": "2024-10-22T00:00:00Z", "participants": [ { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "name": "My Participant", "verifierCertificates": [ { "id": "599bf148-d711-405a-a20b-9c8a87ac8850", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2" } ] } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve participants ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves a list of participants from the requested ecosystem. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_RETRIEVE_LIST_START * ECOSYSTEM_PARTICIPANT_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_PARTICIPANT_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Participants retrieved ```json { "data": [ { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Participant", "identifiers": { "web-semantic": "did:web:example.com", "compact-semantic": "did:web:example.com", "compact": "did:web:example.com", "mobile": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Active", "docTypes": [ "org.iso.18013.5.1" ] } ] }, "isIssuer": false, "isVerifier": false, "isIssuerConstrained": true, "isVerifierConstrained": true, "status": "Active", "country": "US", "stateOrProvince": "US-AL", "organizationAddress": "1234 Main St, City, State, 12345", "organizationPhoneNumber": "012-3456789" } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create participant ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/participants ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Creates a participant in the requested ecosystem. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_CREATE_START * ECOSYSTEM_PARTICIPANT_CREATE_SUCCESS * ECOSYSTEM_PARTICIPANT_CREATE_FAIL ### Request Body ```json { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Participant", "identifiers": { "web-semantic": "did:web:example.com", "compact-semantic": "did:web:example.com", "compact": "did:web:example.com", "mobile": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Active", "docTypes": [ "org.iso.18013.5.1" ] } ] }, "isIssuer": false, "isVerifier": false, "isIssuerConstrained": true, "isVerifierConstrained": true, "status": "Active", "country": "US", "stateOrProvince": "US-AL", "organizationAddress": "1234 Main St, City, State, 12345", "organizationPhoneNumber": "012-3456789" } ``` ### Responses #### 201 - Participant created ```json { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Participant", "identifiers": { "web-semantic": "did:web:example.com", "compact-semantic": "did:web:example.com", "compact": "did:web:example.com", "mobile": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Active", "docTypes": [ "org.iso.18013.5.1" ] } ] }, "isIssuer": false, "isVerifier": false, "isIssuerConstrained": true, "isVerifierConstrained": true, "status": "Active", "country": "US", "stateOrProvince": "US-AL", "organizationAddress": "1234 Main St, City, State, 12345", "organizationPhoneNumber": "012-3456789" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve participant ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants/{participantId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves a participant from the requested ecosystem by its ID. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_RETRIEVE_START * ECOSYSTEM_PARTICIPANT_RETRIEVE_SUCCESS * ECOSYSTEM_PARTICIPANT_RETRIEVE_FAIL ### Responses #### 200 - Participant retrieved ```json { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Participant", "identifiers": { "web-semantic": "did:web:example.com", "compact-semantic": "did:web:example.com", "compact": "did:web:example.com", "mobile": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Active", "docTypes": [ "org.iso.18013.5.1" ] } ] }, "isIssuer": false, "isVerifier": false, "isIssuerConstrained": true, "isVerifierConstrained": true, "status": "Active", "country": "US", "stateOrProvince": "US-AL", "organizationAddress": "1234 Main St, City, State, 12345", "organizationPhoneNumber": "012-3456789" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete participant ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/participants/{participantId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Deletes a participant in the requested ecosystem by its ID. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_DELETE_START * ECOSYSTEM_PARTICIPANT_DELETE_SUCCESS * ECOSYSTEM_PARTICIPANT_DELETE_FAIL ### Responses #### 204 - Participant deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update participant ## Endpoint ``` PUT /v1/ecosystems/{ecosystemId}/participants/{participantId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Updates a participant in the requested ecosystem by its ID. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_UPDATE_START * ECOSYSTEM_PARTICIPANT_UPDATE_SUCCESS * ECOSYSTEM_PARTICIPANT_UPDATE_FAIL ### Request Body ```json { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Participant", "identifiers": { "web-semantic": "did:web:example.com", "compact-semantic": "did:web:example.com", "compact": "did:web:example.com", "mobile": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Active", "docTypes": [ "org.iso.18013.5.1" ] } ] }, "isIssuer": false, "isVerifier": false, "isIssuerConstrained": true, "isVerifierConstrained": true, "status": "Active", "country": "US", "stateOrProvince": "US-AL", "organizationAddress": "1234 Main St, City, State, 12345", "organizationPhoneNumber": "012-3456789" } ``` ### Responses #### 200 - Participant updated ```json { "id": "a24e391a-c27f-4b6e-9805-1ee7e03f3c58", "ecosystemId": "87880d7e-a4d0-462e-8383-3f1e5e16865d", "name": "My Participant", "identifiers": { "web-semantic": "did:web:example.com", "compact-semantic": "did:web:example.com", "compact": "did:web:example.com", "mobile": [ { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Active", "docTypes": [ "org.iso.18013.5.1" ] } ] }, "isIssuer": false, "isVerifier": false, "isIssuerConstrained": true, "isVerifierConstrained": true, "status": "Active", "country": "US", "stateOrProvince": "US-AL", "organizationAddress": "1234 Main St, City, State, 12345", "organizationPhoneNumber": "012-3456789" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve all participant points of contact ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants/{participantId}/contacts ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/contacts` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieve all points of contact for given participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_CONTACT_RETRIEVE_LIST_START * ECOSYSTEM_PARTICIPANT_CONTACT_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_PARTICIPANT_CONTACT_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Participant points of contact retrieved ```json { "data": [ { "id": "920f2489-d953-42f2-b2dd-f37c29b818cf", "fullName": "John Doe", "active": true, "emailAddress": "john.doe@example.com", "primaryPhoneNumber": 1234567890, "secondaryPhoneNumber": 987654321, "createdBy": { "clientId": "f7b9ecfc-8431-470e-a1de-94b68dd92f68", "managementUserId": "ab16dc65-b58e-4be2-ba77-a6f35021cb2b" }, "createdAt": "2025-07-01T00:00:00.000Z", "lastModifiedBy": { "clientId": "f7b9ecfc-8431-470e-a1de-94b68dd92f68", "managementUserId": "ab16dc65-b58e-4be2-ba77-a6f35021cb2b" }, "lastModifiedAt": "2025-07-01T00:00:00.000Z" } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a participant point of contact ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/participants/{participantId}/contacts ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/contacts` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Create a new point of contact for the specified participant. A maximum of 10 contacts can exist per participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_CONTACT_CREATE_START * ECOSYSTEM_PARTICIPANT_CONTACT_CREATE_SUCCESS * ECOSYSTEM_PARTICIPANT_CONTACT_CREATE_FAIL ### Request Body ```json { "fullName": "John Doe", "active": true, "emailAddress": "john.doe@example.com", "primaryPhoneNumber": 1234567890, "secondaryPhoneNumber": 987654321 } ``` ### Responses #### 201 - Participant point of contact created ```json { "id": "920f2489-d953-42f2-b2dd-f37c29b818cf", "fullName": "John Doe", "active": true, "emailAddress": "john.doe@example.com", "primaryPhoneNumber": 1234567890, "secondaryPhoneNumber": 987654321, "createdBy": { "clientId": "f7b9ecfc-8431-470e-a1de-94b68dd92f68", "managementUserId": "ab16dc65-b58e-4be2-ba77-a6f35021cb2b" }, "createdAt": "2025-07-01T00:00:00.000Z", "lastModifiedBy": { "clientId": "f7b9ecfc-8431-470e-a1de-94b68dd92f68", "managementUserId": "ab16dc65-b58e-4be2-ba77-a6f35021cb2b" }, "lastModifiedAt": "2025-07-01T00:00:00.000Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a participant point of contact ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants/{participantId}/contacts/{contactId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/contacts/{contactId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieve a specific point of contact for a given participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_CONTACT_RETRIEVE_START * ECOSYSTEM_PARTICIPANT_CONTACT_RETRIEVE_SUCCESS * ECOSYSTEM_PARTICIPANT_CONTACT_RETRIEVE_FAIL ### Responses #### 200 - Participant point of contact retrieved ```json { "id": "920f2489-d953-42f2-b2dd-f37c29b818cf", "fullName": "John Doe", "active": true, "emailAddress": "john.doe@example.com", "primaryPhoneNumber": 1234567890, "secondaryPhoneNumber": 987654321, "createdBy": { "clientId": "f7b9ecfc-8431-470e-a1de-94b68dd92f68", "managementUserId": "ab16dc65-b58e-4be2-ba77-a6f35021cb2b" }, "createdAt": "2025-07-01T00:00:00.000Z", "lastModifiedBy": { "clientId": "f7b9ecfc-8431-470e-a1de-94b68dd92f68", "managementUserId": "ab16dc65-b58e-4be2-ba77-a6f35021cb2b" }, "lastModifiedAt": "2025-07-01T00:00:00.000Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a participant point of contact ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/participants/{participantId}/contacts/{contactId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/contacts/{contactId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Delete a specific point of contact for a given participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_CONTACT_DELETE_START * ECOSYSTEM_PARTICIPANT_CONTACT_DELETE_SUCCESS * ECOSYSTEM_PARTICIPANT_CONTACT_DELETE_FAIL ### Responses #### 204 - Participant point of contact deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a participant point of contact ## Endpoint ``` PUT /v1/ecosystems/{ecosystemId}/participants/{participantId}/contacts/{contactId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/contacts/{contactId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Update a specific point of contact for a given participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_CONTACT_UPDATE_START * ECOSYSTEM_PARTICIPANT_CONTACT_UPDATE_SUCCESS * ECOSYSTEM_PARTICIPANT_CONTACT_UPDATE_FAIL ### Request Body ```json { "fullName": "John Doe", "active": true, "emailAddress": "john.doe@example.com", "primaryPhoneNumber": 1234567890, "secondaryPhoneNumber": 987654321 } ``` ### Responses #### 200 - Participant point of contact updated ```json { "id": "920f2489-d953-42f2-b2dd-f37c29b818cf", "fullName": "John Doe", "active": true, "emailAddress": "john.doe@example.com", "primaryPhoneNumber": 1234567890, "secondaryPhoneNumber": 987654321, "createdBy": { "clientId": "f7b9ecfc-8431-470e-a1de-94b68dd92f68", "managementUserId": "ab16dc65-b58e-4be2-ba77-a6f35021cb2b" }, "createdAt": "2025-07-01T00:00:00.000Z", "lastModifiedBy": { "clientId": "f7b9ecfc-8431-470e-a1de-94b68dd92f68", "managementUserId": "ab16dc65-b58e-4be2-ba77-a6f35021cb2b" }, "lastModifiedAt": "2025-07-01T00:00:00.000Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all PDF evidence metadata ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieve metadata for all PDF evidence uploaded for given participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_EVIDENCE_RETRIEVE_LIST_START * ECOSYSTEM_PARTICIPANT_EVIDENCE_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_PARTICIPANT_EVIDENCE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - PDF evidence metadata retrieved ```json { "data": [ { "filename": "pdf-evidence.pdf", "fileDescription": "This is an example file description", "id": "920f2489-d953-42f2-b2dd-f37c29b818cf", "uploadedAt": "2025-07-01T00:00:00.000Z" } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Upload PDF evidence ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Upload a PDF file as evidence for the given participant. Maximum file size is 10MB. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_EVIDENCE_CREATE_START * ECOSYSTEM_PARTICIPANT_EVIDENCE_CREATE_SUCCESS * ECOSYSTEM_PARTICIPANT_EVIDENCE_CREATE_FAIL ### Request Body ### Responses #### 201 - Evidence PDF uploaded ```json { "filename": "pdf-evidence.pdf", "fileDescription": "This is an example file description", "id": "920f2489-d953-42f2-b2dd-f37c29b818cf", "uploadedAt": "2025-07-01T00:00:00.000Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a PDF evidence metadata ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence/{evidenceId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence/{evidenceId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieve metadata for a given uploaded PDF evidence. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_EVIDENCE_RETRIEVE_START * ECOSYSTEM_PARTICIPANT_EVIDENCE_RETRIEVE_SUCCESS * ECOSYSTEM_PARTICIPANT_EVIDENCE_RETRIEVE_FAIL ### Responses #### 200 - Evidence retrieved ```json { "filename": "pdf-evidence.pdf", "fileDescription": "This is an example file description", "id": "920f2489-d953-42f2-b2dd-f37c29b818cf", "uploadedAt": "2025-07-01T00:00:00.000Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete PDF evidence ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence/{evidenceId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence/{evidenceId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Delete metadata for a given uploaded PDF evidence and remove it from storage. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_EVIDENCE_DELETE_START * ECOSYSTEM_PARTICIPANT_EVIDENCE_DELETE_SUCCESS * ECOSYSTEM_PARTICIPANT_EVIDENCE_DELETE_FAIL ### Responses #### 204 - PDF Evidence deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update PDF evidence metadata ## Endpoint ``` PUT /v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence/{evidenceId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence/{evidenceId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Update metadata for a given uploaded PDF evidence. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_EVIDENCE_UPDATE_START * ECOSYSTEM_PARTICIPANT_EVIDENCE_UPDATE_SUCCESS * ECOSYSTEM_PARTICIPANT_EVIDENCE_UPDATE_FAIL ### Request Body ### Responses #### 200 - Evidence PDF metadata updated ```json { "filename": "pdf-evidence.pdf", "fileDescription": "This is an example file description", "id": "920f2489-d953-42f2-b2dd-f37c29b818cf", "uploadedAt": "2025-07-01T00:00:00.000Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Download PDF evidence ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence/{evidenceId}/download ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/evidence/{evidenceId}/download` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves a specific PDF evidence file. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_EVIDENCE_DOWNLOAD_START * ECOSYSTEM_PARTICIPANT_EVIDENCE_DOWNLOAD_SUCCESS * ECOSYSTEM_PARTICIPANT_EVIDENCE_DOWNLOAD_FAIL ### Responses #### 200 - PDF Evidence file retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve credential types ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/credentials ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/credentials` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves a list of credential types from the requested ecosystem. ### **Analytic events** * ECOSYSTEM_CREDENTIAL_RETRIEVE_LIST_START * ECOSYSTEM_CREDENTIAL_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_CREDENTIAL_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Credential types retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create credential type ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/credentials ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/credentials` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Creates a new credential type in the requested ecosystem. ### **Analytic events** * ECOSYSTEM_CREDENTIAL_CREATE_START * ECOSYSTEM_CREDENTIAL_CREATE_SUCCESS * ECOSYSTEM_CREDENTIAL_CREATE_FAIL ### Request Body ### Responses #### 201 - Credential type created #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve credential type ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/credentials/{credentialId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/credentials/{credentialId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves a credential type from the requested ecosystem by its ID. ### **Analytic events** * ECOSYSTEM_CREDENTIAL_RETRIEVE_START * ECOSYSTEM_CREDENTIAL_RETRIEVE_SUCCESS * ECOSYSTEM_CREDENTIAL_RETRIEVE_FAIL ### Responses #### 200 - Credential type retrieved #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete credential type ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/credentials/{credentialId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/credentials/{credentialId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Deletes a credential type from the requested ecosystem by its ID. ### **Analytic events** * ECOSYSTEM_CREDENTIAL_DELETE_START * ECOSYSTEM_CREDENTIAL_DELETE_SUCCESS * ECOSYSTEM_CREDENTIAL_DELETE_FAIL ### Responses #### 204 - Credential type deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create issuer assignment ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer/credentials ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer/credentials` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Assigns a credential type to the requested participant in the requested ecosystem. ### **Analytic events** * ECOSYSTEM_ISSUER_POLICY_CREATE_START * ECOSYSTEM_ISSUER_POLICY_CREATE_SUCCESS * ECOSYSTEM_ISSUER_POLICY_CREATE_FAIL ### Request Body ```json { "credentialId": "599bf148-d711-405a-a20b-9c8a87ac8850" } ``` ### Responses #### 201 - Issuer policy created #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Delete issuer assignment ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer/credentials/{credentialId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer/credentials/{credentialId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Removes a credential type to the requested participant in the requested ecosystem. ### **Analytic events** * ECOSYSTEM_ISSUER_POLICY_DELETE_START * ECOSYSTEM_ISSUER_POLICY_DELETE_SUCCESS * ECOSYSTEM_ISSUER_POLICY_DELETE_FAIL ### Responses #### 204 - Issuer policy deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Create verifier assignment ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier/credentials ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier/credentials` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Assigns a credential type to the requested participant in the requested ecosystem. ### **Analytic events** * ECOSYSTEM_VERIFIER_POLICY_CREATE_START * ECOSYSTEM_VERIFIER_POLICY_CREATE_SUCCESS * ECOSYSTEM_VERIFIER_POLICY_CREATE_FAIL ### Request Body ```json { "credentialId": "599bf148-d711-405a-a20b-9c8a87ac8850" } ``` ### Responses #### 201 - Verifier policy created #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Delete verifier assignment ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier/credentials/{credentialId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier/credentials/{credentialId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Removes a credential type from the requested participant in the requested ecosystem.. ### **Analytic events** * ECOSYSTEM_VERIFIER_POLICY_DELETE_START * ECOSYSTEM_VERIFIER_POLICY_DELETE_SUCCESS * ECOSYSTEM_VERIFIER_POLICY_DELETE_FAIL ### Responses #### 204 - Verifier policy deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve all participant issuer certificates ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer-certificates ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer-certificates` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieve all issuer certificates for the specified participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_RETRIEVE_LIST_START * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Participant issuer certificates retrieved ```json { "data": [ { "id": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Inactive", "docTypes": [ { "value": "org.iso.18013.5.1" } ], "linkCertificateInfo": { "issuerCertificateId": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928" } } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a participant issuer certificate ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer-certificates ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer-certificates` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Create a new issuer certificate for the specified participant. The certificatePem field cannot be modified once the certificate is created. To maintain trust continuity across a certificate rotation, provide `linkCertificateInfo` to add this certificate as a successor to a previously uploaded certificate. Link certificates can only be added when the certificate is created, not when it is updated. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_CREATE_START * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_CREATE_SUCCESS * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_CREATE_FAIL ### Request Body ```json { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Inactive", "docTypes": [ { "value": "org.iso.18013.5.1" } ], "linkCertificateInfo": { "issuerCertificateId": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n" } } ``` ### Responses #### 201 - Participant issuer certificate created ```json { "id": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Inactive", "docTypes": [ { "value": "org.iso.18013.5.1" } ], "linkCertificateInfo": { "issuerCertificateId": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a participant issuer certificate ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer-certificates/{issuerCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer-certificates/{issuerCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieve a specific issuer certificate for the specified participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_RETRIEVE_START * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_RETRIEVE_SUCCESS * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - Participant issuer certificate retrieved ```json { "id": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Inactive", "docTypes": [ { "value": "org.iso.18013.5.1" } ], "linkCertificateInfo": { "issuerCertificateId": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a participant issuer certificate ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer-certificates/{issuerCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer-certificates/{issuerCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Delete a specific issuer certificate for the specified participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_DELETE_START * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_DELETE_SUCCESS * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - Participant issuer certificate deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a participant issuer certificate ## Endpoint ``` PUT /v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer-certificates/{issuerCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/issuer-certificates/{issuerCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Update a specific issuer certificate for the specified participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_UPDATE_START * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_UPDATE_SUCCESS * ECOSYSTEM_PARTICIPANT_ISSUER_CERTIFICATE_UPDATE_FAIL ### Request Body ```json { "status": "Active", "docTypes": [ { "value": "org.iso.18013.5.1" } ] } ``` ### Responses #### 200 - Participant issuer certificate updated ```json { "id": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "status": "Inactive", "docTypes": [ { "value": "org.iso.18013.5.1" } ], "linkCertificateInfo": { "issuerCertificateId": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all participant verifier certificates ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieve all verifier certificates for the specified participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_RETRIEVE_LIST_START * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Participant verifier certificates retrieved ```json { "data": [ { "id": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificateFingerprint": "65A98EFAB9A22A1829391B22B668F9F1B758094C1A19D599B516BBF63CBF0015", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICTDCCAfKgAwIBAgIKeKcjIBGvXfS/sjAKBggqhkjOPQQDAjAwMQswCQYDVQQG\r\nEwJVUzEhMB8GA1UEAwwYRXhhbXBsZSBWZXJpZmllciByb290IENBMB4XDTI1MDgy\r\nNzIxMzMxNFoXDTMwMDgyNjIxMzMxNFowMDELMAkGA1UEBhMCVVMxITAfBgNVBAMM\r\nGEV4YW1wbGUgVmVyaWZpZXIgcm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH\r\nA0IABNJHM5ZE+fpVn7b9WwjVBiOiZq9eNXq1JkNj/6ZLe+2GkaRY/WE2Xbg7yx++\r\nh3QEdX3sGKzGO7dygQALBe/4qEyjgfMwgfAwHQYDVR0OBBYEFK8ogqdUH2vZlC1y\r\nNf619a8fnx8KMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMHsG\r\nA1UdHwR0MHIwcKBuoGyGamh0dHBzOi8vbGVhcm4udmlpLmF1MDEubWF0dHIuZ2xv\r\nYmFsL3YyL3ByZXNlbnRhdGlvbnMvY2VydGlmaWNhdGVzL2Q3YzE3ODI4LThkMTgt\r\nNDYyZS1iNDk3LWNjNjI2NWM4ZmQxYi9jcmwwLgYDVR0SBCcwJYYjaHR0cHM6Ly9s\r\nZWFybi52aWkuYXUwMS5tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSAAwRQIhAOqD\r\n0DF3rohBitl5jAj6x1164uGGj6yAhF/eE4aJeGc+AiAgaUYHzobzaPEWd+jZOh/A\r\nq8WgVJ+8sLx9WdJDs9/shQ==\r\n-----END CERTIFICATE-----\r\n", "status": "Inactive", "linkCertificateInfo": { "issuerCertificateId": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928" } } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a participant verifier certificate ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Create a new verifier certificate for the specified participant. The certificatePem field cannot be modified once the certificate is created. To maintain trust continuity across a certificate rotation, provide `linkCertificateInfo` to add this certificate as a successor to a previously uploaded certificate. Link certificates can only be added when the certificate is created, not when it is updated. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_CREATE_START * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_CREATE_SUCCESS * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_CREATE_FAIL ### Request Body ```json { "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICTDCCAfKgAwIBAgIKeKcjIBGvXfS/sjAKBggqhkjOPQQDAjAwMQswCQYDVQQG\r\nEwJVUzEhMB8GA1UEAwwYRXhhbXBsZSBWZXJpZmllciByb290IENBMB4XDTI1MDgy\r\nNzIxMzMxNFoXDTMwMDgyNjIxMzMxNFowMDELMAkGA1UEBhMCVVMxITAfBgNVBAMM\r\nGEV4YW1wbGUgVmVyaWZpZXIgcm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH\r\nA0IABNJHM5ZE+fpVn7b9WwjVBiOiZq9eNXq1JkNj/6ZLe+2GkaRY/WE2Xbg7yx++\r\nh3QEdX3sGKzGO7dygQALBe/4qEyjgfMwgfAwHQYDVR0OBBYEFK8ogqdUH2vZlC1y\r\nNf619a8fnx8KMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMHsG\r\nA1UdHwR0MHIwcKBuoGyGamh0dHBzOi8vbGVhcm4udmlpLmF1MDEubWF0dHIuZ2xv\r\nYmFsL3YyL3ByZXNlbnRhdGlvbnMvY2VydGlmaWNhdGVzL2Q3YzE3ODI4LThkMTgt\r\nNDYyZS1iNDk3LWNjNjI2NWM4ZmQxYi9jcmwwLgYDVR0SBCcwJYYjaHR0cHM6Ly9s\r\nZWFybi52aWkuYXUwMS5tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSAAwRQIhAOqD\r\n0DF3rohBitl5jAj6x1164uGGj6yAhF/eE4aJeGc+AiAgaUYHzobzaPEWd+jZOh/A\r\nq8WgVJ+8sLx9WdJDs9/shQ==\r\n-----END CERTIFICATE-----\r\n", "status": "Inactive", "linkCertificateInfo": { "issuerCertificateId": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n" } } ``` ### Responses #### 201 - Participant verifier certificate created ```json { "id": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificateFingerprint": "65A98EFAB9A22A1829391B22B668F9F1B758094C1A19D599B516BBF63CBF0015", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICTDCCAfKgAwIBAgIKeKcjIBGvXfS/sjAKBggqhkjOPQQDAjAwMQswCQYDVQQG\r\nEwJVUzEhMB8GA1UEAwwYRXhhbXBsZSBWZXJpZmllciByb290IENBMB4XDTI1MDgy\r\nNzIxMzMxNFoXDTMwMDgyNjIxMzMxNFowMDELMAkGA1UEBhMCVVMxITAfBgNVBAMM\r\nGEV4YW1wbGUgVmVyaWZpZXIgcm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH\r\nA0IABNJHM5ZE+fpVn7b9WwjVBiOiZq9eNXq1JkNj/6ZLe+2GkaRY/WE2Xbg7yx++\r\nh3QEdX3sGKzGO7dygQALBe/4qEyjgfMwgfAwHQYDVR0OBBYEFK8ogqdUH2vZlC1y\r\nNf619a8fnx8KMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMHsG\r\nA1UdHwR0MHIwcKBuoGyGamh0dHBzOi8vbGVhcm4udmlpLmF1MDEubWF0dHIuZ2xv\r\nYmFsL3YyL3ByZXNlbnRhdGlvbnMvY2VydGlmaWNhdGVzL2Q3YzE3ODI4LThkMTgt\r\nNDYyZS1iNDk3LWNjNjI2NWM4ZmQxYi9jcmwwLgYDVR0SBCcwJYYjaHR0cHM6Ly9s\r\nZWFybi52aWkuYXUwMS5tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSAAwRQIhAOqD\r\n0DF3rohBitl5jAj6x1164uGGj6yAhF/eE4aJeGc+AiAgaUYHzobzaPEWd+jZOh/A\r\nq8WgVJ+8sLx9WdJDs9/shQ==\r\n-----END CERTIFICATE-----\r\n", "status": "Inactive", "linkCertificateInfo": { "issuerCertificateId": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a participant verifier certificate ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates/{verifierCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates/{verifierCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieve a specific verifier certificate for the specified participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_RETRIEVE_START * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_RETRIEVE_SUCCESS * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - Participant verifier certificate retrieved ```json { "id": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificateFingerprint": "65A98EFAB9A22A1829391B22B668F9F1B758094C1A19D599B516BBF63CBF0015", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICTDCCAfKgAwIBAgIKeKcjIBGvXfS/sjAKBggqhkjOPQQDAjAwMQswCQYDVQQG\r\nEwJVUzEhMB8GA1UEAwwYRXhhbXBsZSBWZXJpZmllciByb290IENBMB4XDTI1MDgy\r\nNzIxMzMxNFoXDTMwMDgyNjIxMzMxNFowMDELMAkGA1UEBhMCVVMxITAfBgNVBAMM\r\nGEV4YW1wbGUgVmVyaWZpZXIgcm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH\r\nA0IABNJHM5ZE+fpVn7b9WwjVBiOiZq9eNXq1JkNj/6ZLe+2GkaRY/WE2Xbg7yx++\r\nh3QEdX3sGKzGO7dygQALBe/4qEyjgfMwgfAwHQYDVR0OBBYEFK8ogqdUH2vZlC1y\r\nNf619a8fnx8KMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMHsG\r\nA1UdHwR0MHIwcKBuoGyGamh0dHBzOi8vbGVhcm4udmlpLmF1MDEubWF0dHIuZ2xv\r\nYmFsL3YyL3ByZXNlbnRhdGlvbnMvY2VydGlmaWNhdGVzL2Q3YzE3ODI4LThkMTgt\r\nNDYyZS1iNDk3LWNjNjI2NWM4ZmQxYi9jcmwwLgYDVR0SBCcwJYYjaHR0cHM6Ly9s\r\nZWFybi52aWkuYXUwMS5tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSAAwRQIhAOqD\r\n0DF3rohBitl5jAj6x1164uGGj6yAhF/eE4aJeGc+AiAgaUYHzobzaPEWd+jZOh/A\r\nq8WgVJ+8sLx9WdJDs9/shQ==\r\n-----END CERTIFICATE-----\r\n", "status": "Inactive", "linkCertificateInfo": { "issuerCertificateId": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a participant verifier certificate ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates/{verifierCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates/{verifierCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Delete a specific verifier certificate for the specified participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_DELETE_START * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_DELETE_SUCCESS * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - Participant verifier certificate deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a participant verifier certificate ## Endpoint ``` PUT /v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates/{verifierCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/participants/{participantId}/verifier-certificates/{verifierCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Update a specific verifier certificate for the specified participant. ### **Analytic events** * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_UPDATE_START * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_UPDATE_SUCCESS * ECOSYSTEM_PARTICIPANT_VERIFIER_CERTIFICATE_UPDATE_FAIL ### Request Body ```json { "status": "Active" } ``` ### Responses #### 200 - Participant verifier certificate updated ```json { "id": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificateFingerprint": "65A98EFAB9A22A1829391B22B668F9F1B758094C1A19D599B516BBF63CBF0015", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIICTDCCAfKgAwIBAgIKeKcjIBGvXfS/sjAKBggqhkjOPQQDAjAwMQswCQYDVQQG\r\nEwJVUzEhMB8GA1UEAwwYRXhhbXBsZSBWZXJpZmllciByb290IENBMB4XDTI1MDgy\r\nNzIxMzMxNFoXDTMwMDgyNjIxMzMxNFowMDELMAkGA1UEBhMCVVMxITAfBgNVBAMM\r\nGEV4YW1wbGUgVmVyaWZpZXIgcm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEH\r\nA0IABNJHM5ZE+fpVn7b9WwjVBiOiZq9eNXq1JkNj/6ZLe+2GkaRY/WE2Xbg7yx++\r\nh3QEdX3sGKzGO7dygQALBe/4qEyjgfMwgfAwHQYDVR0OBBYEFK8ogqdUH2vZlC1y\r\nNf619a8fnx8KMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMHsG\r\nA1UdHwR0MHIwcKBuoGyGamh0dHBzOi8vbGVhcm4udmlpLmF1MDEubWF0dHIuZ2xv\r\nYmFsL3YyL3ByZXNlbnRhdGlvbnMvY2VydGlmaWNhdGVzL2Q3YzE3ODI4LThkMTgt\r\nNDYyZS1iNDk3LWNjNjI2NWM4ZmQxYi9jcmwwLgYDVR0SBCcwJYYjaHR0cHM6Ly9s\r\nZWFybi52aWkuYXUwMS5tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSAAwRQIhAOqD\r\n0DF3rohBitl5jAj6x1164uGGj6yAhF/eE4aJeGc+AiAgaUYHzobzaPEWd+jZOh/A\r\nq8WgVJ+8sLx9WdJDs9/shQ==\r\n-----END CERTIFICATE-----\r\n", "status": "Inactive", "linkCertificateInfo": { "issuerCertificateId": "3b5b1a6a-1b1a-4b1a-8b1a-1b1a4b1a8b1a", "certificatePem": "-----BEGIN CERTIFICATE-----\r\nMIIBwzCCAWigAwIBAgIKRGC+CqoTGJKkkTAKBggqhkjOPQQDAjAgMR4wCQYDVQQG\r\nEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwHhcNMjMwODA4MDAwOTIxWhcNMzMwODA1\r\nMDAwOTIxWjAgMR4wCQYDVQQGEwJOWjARBgNVBAMTCk1BVFRSIElBQ0EwWTATBgcq\r\nhkjOPQIBBggqhkjOPQMBBwNCAASRu69fzdgM4odkyPtRcZd3eGWCw4BB7StZNGRm\r\nuIlrraUyv9SWPHgUYjYmRB1g7ERzj/pOSAspk71Y+QA+j9nPo4GJMIGGMBIGA1Ud\r\nEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgAGMB0GA1UdDgQWBBSONcHGh4If\r\nO1dYorRpsuFrs+f8SDAcBgNVHRIEFTATgRFpbmZvQG1hdHRyLmdsb2JhbDAjBgNV\r\nHR8EHDAaMBiiFoYUaHR0cHM6Ly9tYXR0ci5nbG9iYWwwCgYIKoZIzj0EAwIDSQAw\r\nRgIhAPKJIGDSvp7VxRBLCWWeghqi8UUeO+dZsC49TUZcDMNxAiEAoh+7dT+l+GzX\r\nk0J2SoGmPiagrbAuIYyTHwzZZuYr1W4=\r\n-----END CERTIFICATE-----\r\n", "certificateFingerprint": "57B178A6C2B8C1877DBA515AD4FD60F9C805EFC309287182DB7DEBFE43A22928" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create VICAL ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/vicals ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Creates a Verified Issuer Certificate Authority List (VICAL) based on the policy of the requested ecosystem. Refer to VICAL to learn more about the [VICAL](https://learn.mattr.global/docs/digital-trust-service/vical-overview) purpose and data structure. ### Responses #### 201 - VICAL created ```json { "vicalIssueID": 1337, "date": "2024-07-28T23:01:13.000Z" } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve all VICALs ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/vicals/public ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public` ### Authorization None required. ## Description Retrieves all VICALs available in the requested ecosystem. This endpoint is intended for public consumption, and as such does not require authentication. ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - VICALs retrieved ```json { "data": [ { "vicalIssueID": 1337, "date": "2024-07-28T23:01:13.000Z", "filename": "vical-2024-07-28-1722164473000.cbor", "isAutoPublished": false } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve latest VICAL ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/vicals/public/latest ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public/latest` ### Authorization None required. ## Description Retrieves the latest VICAL from the requested ecosystem. This endpoint is intended for public consumption, and as such does not require authentication. ### Responses #### 200 - VICAL retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve specific VICAL ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/vicals/public/{vicalIssueId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public/{vicalIssueId}` ### Authorization None required. ## Description Retrieves a specific VICAL from the requested ecosystem by providing the VICAL version identifier. This endpoint is intended for public consumption, and as such does not require authentication. ### Responses #### 200 - VICAL retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve VICAL configuration ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/vicals/configuration ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/configuration` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieve the VICAL configuration from the requested ecosystem ### Responses #### 200 - VICAL configuration retrieved ```json { "vicalProvider": "Mattr", "autoPublish": { "configuredAt": "2025-07-01T00:00:00.000Z" } } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete VICAL configuration ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/vicals/configuration ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/configuration` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Delete the VICAL configuration for the requested ecosystem ### Responses #### 204 - VICAL configuration deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update VICAL configuration ## Endpoint ``` PUT /v1/ecosystems/{ecosystemId}/vicals/configuration ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/configuration` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Update the VICAL configuration for the requested ecosystem ### Request Body ```json { "vicalProvider": "Mattr", "autoPublish": { "enabled": true, "frequency": "Daily" } } ``` ### Responses #### 200 - VICAL configuration updated ```json { "vicalProvider": "Mattr", "autoPublish": { "configuredAt": "2025-07-01T00:00:00.000Z" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all DTS root CA certificates ## Endpoint ``` GET /v1/ecosystems/certificates/ca ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves all DTS root CA certificates. ### **Analytic events** * ECOSYSTEM_DTS_CA_CERTIFICATE_RETRIEVE_LIST_START * ECOSYSTEM_DTS_CA_CERTIFICATE_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_DTS_CA_CERTIFICATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - DTS root CA certificates retrieved ```json { "data": [ { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "organisationName": "Example Inc.", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" }, "isManaged": true, "useIntermediateCa": true } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a DTS root CA certificate ## Endpoint ``` POST /v1/ecosystems/certificates/ca ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Creates a DTS root CA certificate which is used to sign DTS intermediate and/or signer certificates. - A maximum of three DTS root CA certificates can be created per tenant. ### **Analytic events** * ECOSYSTEM_DTS_CA_CERTIFICATE_CREATE_START * ECOSYSTEM_DTS_CA_CERTIFICATE_CREATE_SUCCESS * ECOSYSTEM_DTS_CA_CERTIFICATE_CREATE_FAIL ### Request Body ### Responses #### 201 - DTS root CA certificate created ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "organisationName": "Example Inc.", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" }, "isManaged": true, "useIntermediateCa": true } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 409 - Maximum number of DTS CA certificates reached. Please delete an existing certificate before creating a new one. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a DTS root CA certificate ## Endpoint ``` GET /v1/ecosystems/certificates/ca/{dtsCaCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca/{dtsCaCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves a DTS root CA certificate. ### **Analytic events** * ECOSYSTEM_DTS_CA_CERTIFICATE_RETRIEVE_START * ECOSYSTEM_DTS_CA_CERTIFICATE_RETRIEVE_SUCCESS * ECOSYSTEM_DTS_CA_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - DTS root CA certificate retrieved ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "organisationName": "Example Inc.", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" }, "isManaged": true, "useIntermediateCa": true } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a DTS root CA certificate ## Endpoint ``` DELETE /v1/ecosystems/certificates/ca/{dtsCaCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca/{dtsCaCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Deletes a DTS root CA certificate. ### **Analytic events** * ECOSYSTEM_DTS_CA_CERTIFICATE_DELETE_START * ECOSYSTEM_DTS_CA_CERTIFICATE_DELETE_SUCCESS * ECOSYSTEM_DTS_CA_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - DTS root CA certificate deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a DTS root CA certificate ## Endpoint ``` PUT /v1/ecosystems/certificates/ca/{dtsCaCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca/{dtsCaCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Updates a DTS root CA certificate. ### **Analytic events** * ECOSYSTEM_DTS_CA_CERTIFICATE_UPDATE_START * ECOSYSTEM_DTS_CA_CERTIFICATE_UPDATE_SUCCESS * ECOSYSTEM_DTS_CA_CERTIFICATE_UPDATE_FAIL ### Request Body ```json { "active": true, "useIntermediateCa": true } ``` ### Responses #### 200 - DTS root CA certificate updated ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "organisationName": "Example Inc.", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" }, "isManaged": true, "useIntermediateCa": true } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a DTS root CA certificate revocation list ## Endpoint ``` GET /v1/ecosystems/certificates/ca/{dtsCaCertificateId}/crl ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca/{dtsCaCertificateId}/crl` ### Authorization None required. ## Description Retrieves the revocation list for a given DTS root CA certificate. This endpoint is intended for public consumption, and as such does not require authentication. ### Responses #### 200 - DTS root CA certificate revocation list retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all public DTS root CA certificates ## Endpoint ``` GET /v1/ecosystems/public/certificates/ca ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/public/certificates/ca` ### Authorization None required. ## Description Retrieves all public DTS root CA certificates. This endpoint is intended for public consumption, and as such does not require authentication. ### Responses #### 200 - Public DTS root CA certificates retrieved ```json { "rootCertificates": [ { "certificate": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "notBefore": "2023-10-22T00:00:00Z", "notAfter": "2024-10-22T00:00:00Z", "fingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "commonName": "MATTR DTS Root CA", "intermediateCertificates": [ { "certificate": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "notBefore": "2023-10-22T00:00:00Z", "notAfter": "2024-10-22T00:00:00Z", "fingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "commonName": "MATTR DTS Intermediate CA" } ] } ] } ``` # Retrieve DTS root CA certificate ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/vicals/public/certificates/ca/latest ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public/certificates/ca/latest` ### Authorization None required. ## Description Retrieves the latest DTS root CA certificate. This can be used by relying parties to verify a signed VICAL. This endpoint is intended for public consumption, and as such does not require authentication. ### Responses #### 200 - DTS root CA certificate retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve DTS root CA certificate revocation list ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/vicals/public/certificates/ca/{caCertificateId}/crl ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/vicals/public/certificates/ca/{caCertificateId}/crl` ### Authorization None required. ## Description Retrieves revocation list for a given DTS root CA certificate. This endpoint is intended for public consumption, and as such does not require authentication. ### Responses #### 200 - Revocation list retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all DTS intermediate CA certificates under a root CA ## Endpoint ``` GET /v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves all DTS intermediate CA certificates issued under the specified DTS root CA. ### **Analytic events** * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_RETRIEVE_LIST_START * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - DTS intermediate CA certificates retrieved ```json { "data": [ { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "organisationName": "Example Inc.", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" } } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a DTS intermediate CA certificate ## Endpoint ``` POST /v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Creates a DTS intermediate CA certificate, which sits between the DTS root CA certificate and the signer certificate to sign DTS signer certificates. DTS intermediate CA certificates are only relevant when using the 3-tier certificate model, where a DTS root CA signs an intermediate CA, which in turn signs the signer certificate. They are only supported for unmanaged (external) DTS root CA certificates that have `useIntermediateCa` set to `true`. For an explanation of the 2-tier and 3-tier models and guidance on choosing between them, refer to the [DTS certificates overview](https://learn.mattr.global/docs/digital-trust-service/certificates-overview#certificate-models-2-tier-and-3-tier). ### **Analytic events** * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_CREATE_START * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_CREATE_SUCCESS * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_CREATE_FAIL ### Request Body ### Responses #### 201 - DTS intermediate CA certificate created ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "organisationName": "Example Inc.", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a DTS intermediate CA certificate ## Endpoint ``` GET /v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate/{dtsIntermediateCaCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate/{dtsIntermediateCaCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves a DTS intermediate CA certificate. ### **Analytic events** * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_RETRIEVE_START * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_RETRIEVE_SUCCESS * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - DTS intermediate CA certificate retrieved ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "organisationName": "Example Inc.", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" } } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a DTS intermediate CA certificate ## Endpoint ``` DELETE /v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate/{dtsIntermediateCaCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate/{dtsIntermediateCaCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Deletes a DTS intermediate CA certificate. ### **Analytic events** * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_DELETE_START * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_DELETE_SUCCESS * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - DTS intermediate CA certificate deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a DTS intermediate CA certificate ## Endpoint ``` PUT /v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate/{dtsIntermediateCaCertificateId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/ca/{dtsCaCertificateId}/intermediate/{dtsIntermediateCaCertificateId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Updates a DTS intermediate CA certificate. ### **Analytic events** * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_UPDATE_START * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_UPDATE_SUCCESS * ECOSYSTEM_DTS_INTERMEDIATE_CA_CERTIFICATE_UPDATE_FAIL ### Request Body ```json { "active": true } ``` ### Responses #### 200 - DTS intermediate CA certificate updated ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "organisationName": "Example Inc.", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all VICAL signers ## Endpoint ``` GET /v1/ecosystems/certificates/vical-signers ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/vical-signers` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves all VICAL signers. ### **Analytic events** * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_RETRIEVE_LIST_START * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - VICAL signers retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a VICAL signer ## Endpoint ``` POST /v1/ecosystems/certificates/vical-signers ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/vical-signers` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Creates a VICAL signer. - Only available in implementations using unmanaged (external) DTS root CA certificates. - A maximum of five VICAL signers can be created per tenant. ### **Analytic events** * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_CREATE_START * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_CREATE_SUCCESS * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_CREATE_FAIL ### Request Body ```json { "caId": "b0aae560-10e7-4247-8e96-7cdd3578a1e2", "intermediateCaId": "b0aae560-10e7-4247-8e96-7cdd3578a1e2" } ``` ### Responses #### 201 - VICAL signer created ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "csrPem": "-----BEGIN CERTIFICATE REQUEST-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE REQUEST-----", "caId": "b0aae560-10e7-4247-8e96-7cdd3578a1e2", "intermediateCaId": "b0aae560-10e7-4247-8e96-7cdd3578a1e2", "active": false } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 409 - Maximum number of VICAL signer certificates reached. Please delete an existing certificate before creating a new one. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a VICAL signer ## Endpoint ``` GET /v1/ecosystems/certificates/vical-signers/{vicalSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/vical-signers/{vicalSignerId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves a VICAL signer. ### **Analytic events** * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_RETRIEVE_START * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_RETRIEVE_SUCCESS * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - VICAL signer retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a VICAL signer ## Endpoint ``` DELETE /v1/ecosystems/certificates/vical-signers/{vicalSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/vical-signers/{vicalSignerId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Deletes a VICAL signer. Only available in implementations using unmanaged (external) DTS root CA certificates. ### **Analytic events** * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_DELETE_START * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_DELETE_SUCCESS * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - VICAL signer deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a VICAL signer ## Endpoint ``` PUT /v1/ecosystems/certificates/vical-signers/{vicalSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/vical-signers/{vicalSignerId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Updates a VICAL signer by: - Providing a VICAL Signer Certificate (VSC) in PEM format that matches its Certificate Signing Request (CSR). - Activating or deactivating the VICAL signer. Only VICAL signers with a valid `certificatePem` can be activated. - The `certificatePem` field becomes immutable after it's updated for the first time. Only available in implementations using unmanaged (external) DTS root CA certificates. ### **Analytic events** * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_UPDATE_START * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_UPDATE_SUCCESS * ECOSYSTEM_VICAL_SIGNER_CERTIFICATE_UPDATE_FAIL ### Request Body ```json { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----" } ``` ### Responses #### 200 - VICAL signer updated ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" }, "caId": "b0aae560-10e7-4247-8e96-7cdd3578a1e2", "intermediateCaId": "b0aae560-10e7-4247-8e96-7cdd3578a1e2" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create RICAL ## Endpoint ``` POST /v1/ecosystems/{ecosystemId}/ricals ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Creates a Reader Identity Certificate Authority List (RICAL) based on the policy of the requested ecosystem. A [RICAL configuration](#operation/updateRicalConfiguration) is required in order for RICALs to be created. ### **Analytic events** * ECOSYSTEM_RICAL_CREATE_START * ECOSYSTEM_RICAL_CREATE_SUCCESS * ECOSYSTEM_RICAL_CREATE_FAIL ### Responses #### 201 - RICAL created ```json { "ricalIssueID": 1337, "date": "2024-07-28T23:01:13.000Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve all RICALs ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/ricals/public ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals/public` ### Authorization None required. ## Description Retrieves all RICALs available in the requested ecosystem. The response is a JSON list of the published RICALs and their version identifiers. To download an actual RICAL file (a CBOR encoded file matching the RICAL format defined in ISO/IEC 18013-5), call the [Retrieve specific RICAL](#operation/getRical) or [Retrieve latest RICAL](#operation/getLatestRical) endpoint. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * ECOSYSTEM_RICAL_RETRIEVE_LIST_START * ECOSYSTEM_RICAL_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_RICAL_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - RICALs retrieved ```json { "data": [ { "ricalIssueID": 1337, "date": "2024-07-28T23:01:13.000Z", "filename": "rical-2024-07-28-1722164473000.cbor", "isAutoPublished": false } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve latest RICAL ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/ricals/public/latest ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals/public/latest` ### Authorization None required. ## Description Retrieves the latest RICAL from the requested ecosystem. The response is a CBOR encoded file matching the RICAL format defined in ISO/IEC 18013-5. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * ECOSYSTEM_RICAL_RETRIEVE_LATEST_START * ECOSYSTEM_RICAL_RETRIEVE_LATEST_SUCCESS * ECOSYSTEM_RICAL_RETRIEVE_LATEST_FAIL ### Responses #### 200 - RICAL retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve specific RICAL ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/ricals/public/{ricalIssueId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals/public/{ricalIssueId}` ### Authorization None required. ## Description Retrieves a specific RICAL from the requested ecosystem by providing the RICAL version identifier. The response is a CBOR encoded file matching the RICAL format defined in ISO/IEC 18013-5. This endpoint is intended for public consumption, and as such does not require authentication. ### **Analytic events** * ECOSYSTEM_RICAL_RETRIEVE_START * ECOSYSTEM_RICAL_RETRIEVE_SUCCESS * ECOSYSTEM_RICAL_RETRIEVE_FAIL ### Responses #### 200 - RICAL retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve RICAL configuration ## Endpoint ``` GET /v1/ecosystems/{ecosystemId}/ricals/configuration ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals/configuration` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieve the RICAL configuration from the requested ecosystem. ### **Analytic events** * ECOSYSTEM_RICAL_CONFIGURATION_RETRIEVE_START * ECOSYSTEM_RICAL_CONFIGURATION_RETRIEVE_SUCCESS * ECOSYSTEM_RICAL_CONFIGURATION_RETRIEVE_FAIL ### Responses #### 200 - RICAL configuration retrieved ```json { "ricalProvider": "MATTR", "autoPublish": { "configuredAt": "2025-07-01T00:00:00.000Z" } } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete RICAL configuration ## Endpoint ``` DELETE /v1/ecosystems/{ecosystemId}/ricals/configuration ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals/configuration` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Delete the RICAL configuration for the requested ecosystem. ### **Analytic events** * ECOSYSTEM_RICAL_CONFIGURATION_DELETE_START * ECOSYSTEM_RICAL_CONFIGURATION_DELETE_SUCCESS * ECOSYSTEM_RICAL_CONFIGURATION_DELETE_FAIL ### Responses #### 204 - RICAL configuration deleted #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update RICAL configuration ## Endpoint ``` PUT /v1/ecosystems/{ecosystemId}/ricals/configuration ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/{ecosystemId}/ricals/configuration` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Update the RICAL configuration for the requested ecosystem. A RICAL configuration is required in order for RICALs to be created. ### **Analytic events** * ECOSYSTEM_RICAL_CONFIGURATION_UPSERT_START * ECOSYSTEM_RICAL_CONFIGURATION_UPSERT_SUCCESS * ECOSYSTEM_RICAL_CONFIGURATION_UPSERT_FAIL ### Request Body ```json { "ricalProvider": "MATTR", "autoPublish": { "enabled": true, "frequency": "Daily" } } ``` ### Responses #### 200 - RICAL configuration updated ```json { "ricalProvider": "MATTR", "autoPublish": { "configuredAt": "2025-07-01T00:00:00.000Z" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all RICAL signers ## Endpoint ``` GET /v1/ecosystems/certificates/rical-signers ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/rical-signers` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves all RICAL signers. ### **Analytic events** * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_RETRIEVE_LIST_START * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_RETRIEVE_LIST_SUCCESS * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - RICAL signers retrieved ```json { "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create a RICAL signer ## Endpoint ``` POST /v1/ecosystems/certificates/rical-signers ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/rical-signers` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Creates a RICAL signer. - Only available in implementations using unmanaged (external) DTS root CA certificates. - A maximum of five RICAL signers can be created per tenant. Provide either `caId` or `intermediateCaId`. ### **Analytic events** * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_CREATE_START * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_CREATE_SUCCESS * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_CREATE_FAIL ### Request Body ```json { "caId": "b0aae560-10e7-4247-8e96-7cdd3578a1e2", "intermediateCaId": "c1bbe671-21f8-5358-9537-eed4669b43f3" } ``` ### Responses #### 201 - RICAL signer created ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "csrPem": "-----BEGIN CERTIFICATE REQUEST-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE REQUEST-----", "caId": "b0aae560-10e7-4247-8e96-7cdd3578a1e2", "intermediateCaId": "c1bbe671-21f8-5358-9537-eed4669b43f3", "active": false } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 409 - Maximum number of RICAL signer certificates reached. Please delete an existing certificate before creating a new one. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve a RICAL signer ## Endpoint ``` GET /v1/ecosystems/certificates/rical-signers/{ricalSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/rical-signers/{ricalSignerId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Retrieves a RICAL signer. ### **Analytic events** * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_RETRIEVE_START * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_RETRIEVE_SUCCESS * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_RETRIEVE_FAIL ### Responses #### 200 - RICAL signer retrieved #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete a RICAL signer ## Endpoint ``` DELETE /v1/ecosystems/certificates/rical-signers/{ricalSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/rical-signers/{ricalSignerId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Deletes a RICAL signer. Deleting a RICAL signer does not affect any RICALs it has already signed — those remain valid. It only means this signer can no longer be used to sign new RICALs. Only available in implementations using unmanaged (external) DTS root CA certificates. ### **Analytic events** * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_DELETE_START * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_DELETE_SUCCESS * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_DELETE_FAIL ### Responses #### 204 - RICAL signer deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update a RICAL signer ## Endpoint ``` PUT /v1/ecosystems/certificates/rical-signers/{ricalSignerId} ``` Full URL: `https://example.vii.au01.mattr.global/v1/ecosystems/certificates/rical-signers/{ricalSignerId}` ### Authorization Bearer token required. Required roles: admin, dts-provider ## Description Updates a RICAL signer by: - Providing a RICAL Signer Certificate in PEM format that matches its Certificate Signing Request (CSR). - Activating or deactivating the RICAL signer. Only RICAL signers with a valid `certificatePem` can be activated. - The `certificatePem` field becomes immutable after it's updated for the first time. Only available in implementations using unmanaged (external) DTS root CA certificates. ### **Analytic events** * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_UPDATE_START * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_UPDATE_SUCCESS * ECOSYSTEM_RICAL_SIGNER_CERTIFICATE_UPDATE_FAIL ### Request Body ```json { "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----" } ``` ### Responses #### 200 - RICAL signer updated ```json { "id": "782f1885-c7c2-4459-8426-b6d7c111b0b1", "active": true, "certificatePem": "-----BEGIN CERTIFICATE-----\nMIIDXTCCAkWgAwIBAgIJAL5...\n-----END CERTIFICATE-----", "certificateFingerprint": "f6cad6e579d70b3973efa60624af731a580d1a11a7579e70f2f10f059dc86172", "certificateData": { "commonName": "example.com", "country": "US", "notAfter": "2024-10-22T00:00:00Z", "notBefore": "2023-10-22T00:00:00Z" } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve all Webhooks ## Endpoint ``` GET /v1/webhooks ``` Full URL: `https://example.vii.au01.mattr.global/v1/webhooks` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieves a list of webhooks configured on the tenant. ### **Analytic events** * WEBHOOK_RETRIEVE_LIST_START * WEBHOOK_RETRIEVE_LIST_SUCCESS * WEBHOOK_RETRIEVE_LIST_FAIL ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Webhooks retrieved ```json { "data": [ { "id": "0c099611-19c4-4f29-8724-6b9e5ba1ef7c", "events": [ "OpenIdCredentialIssued" ], "url": "https://example.com" } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjItMDgtMjJUMDElM0E1OSUzQTE5LjYyMFomaWQ9MGMwOTk2MTEtMTljNC00ZjI5LTg3MjQtNmI5ZTViYTFlZjdj" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Create Webhook ## Endpoint ``` POST /v1/webhooks ``` Full URL: `https://example.vii.au01.mattr.global/v1/webhooks` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Creates a new webhook for this tenant. ### **Analytic events** * WEBHOOK_CREATE_START * WEBHOOK_CREATE_SUCCESS * WEBHOOK_CREATE_FAIL ### Request Body The webhook payload ```json { "events": [ "OpenIdCredentialIssued" ], "url": "https://example.com" } ``` ### Responses #### 201 - Webhook created ```json { "id": "0c099611-19c4-4f29-8724-6b9e5ba1ef7c", "events": [ "OpenIdCredentialIssued" ], "url": "https://example.com" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve Webhook ## Endpoint ``` GET /v1/webhooks/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/webhooks/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Retrieve a specific Webhook by providing its ID. ### **Analytic events** * WEBHOOK_RETRIEVE_START * WEBHOOK_RETRIEVE_SUCCESS * WEBHOOK_RETRIEVE_FAIL ### Path Parameters - `id`: string (required) The requested Webhook ID. ### Responses #### 200 - Webhook retrieved ```json { "id": "0c099611-19c4-4f29-8724-6b9e5ba1ef7c", "events": [ "OpenIdCredentialIssued" ], "url": "https://example.com" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Delete Webhook ## Endpoint ``` DELETE /v1/webhooks/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/webhooks/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Deletes a Webhook by providing its ID. ### **Analytic events** * WEBHOOK_DELETE_START * WEBHOOK_DELETE_SUCCESS * WEBHOOK_DELETE_FAIL ### Path Parameters - `id`: string (required) Webhook ID ### Responses #### 204 - Webhook deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Update Webhook ## Endpoint ``` PUT /v1/webhooks/{id} ``` Full URL: `https://example.vii.au01.mattr.global/v1/webhooks/{id}` ### Authorization Bearer token required. Required roles: admin, issuer, managed-issuer ## Description Updates an existing Webhook by providing its ID. ### **Analytic events** * WEBHOOK_UPDATE_START * WEBHOOK_UPDATE_SUCCESS * WEBHOOK_UPDATE_FAIL ### Path Parameters - `id`: string (required) Webhook ID ### Request Body Update Webhook ```json { "events": [ "OpenIdCredentialIssued" ], "url": "https://example.com" } ``` ### Responses #### 200 - Webhook updated ```json { "id": "0c099611-19c4-4f29-8724-6b9e5ba1ef7c", "events": [ "OpenIdCredentialIssued" ], "url": "https://example.com" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` # Retrieve Webhook JWKs ## Endpoint ``` GET /v1/webhooks/jwks ``` Full URL: `https://example.vii.au01.mattr.global/v1/webhooks/jwks` ### Authorization None required. ## Description Retrieves a list of Webhook JWKs (JSON Web Keys) from the tenant. These keys can be used to verify the HTTP signature and validate the integrity and authorship of generated Webhooks. This endpoint is intended for public consumption, and as such does not require authentication. ### Responses #### 200 - Webhook JWKs retrieved # Create API Auth Token ## Endpoint ``` POST /oauth/token ``` Full URL: `https://example.vii.au01.mattr.global/oauth/token` ### Authorization None required. ## Description Authorization endpoint for gaining token used for API requests requiring `bearerAuth`. You will be provided the required `client_id` and `client_secret` as part of onboarding. > The returned bearer token will only enable access to endpoints as per your client's defined role. Refer to [Access Control](https://learn.mattr.global/docs/platform-management/access-control) for more information. ### Request Body ```json { "client_id": "htf792W4p4MedZbnoWAs51EfqUt4d2", "client_secret": "d3fYDX7FjPg1D1h2viARXsolPByQ9vMfg8LHylBy8F4s5KJLB4HhHGOxxqJnSj3G", "audience": "https://learn.vii.au01.mattr.global", "grant_type": "client_credentials" } ``` ### Responses #### 200 - Successful response ```json { "access_token": "s2dgbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6s2dcaEROemRDf5gbRVEwTTVSVFE0TmtZME9UZzVNVEpDTlVJNFJqRTBPREExTmpZMk1qazFPQSJ9", "expires_in": 14400, "token_type": "Bearer" } ``` #### 401 - Unauthorized # Create API Auth Token ## Endpoint ``` POST /oauth/token ``` Full URL: `https://example.vii.au01.mattr.global/oauth/token` ### Authorization None required. ## Description Authorization endpoint for gaining token used for API requests requiring `bearerAuth`. You will be provided the required `client_id` and `client_secret` as part of onboarding. > The returned bearer token will only enable access to endpoints as per your client's defined role. Refer to [Access Control](https://learn.mattr.global/docs/platform-management/access-control) for more information. ### Request Body ```json { "client_id": "htf792W4p4MedZbnoWAs51EfqUt4d2", "client_secret": "d3fYDX7FjPg1D1h2viARXsolPByQ9vMfg8LHylBy8F4s5KJLB4HhHGOxxqJnSj3G", "audience": "https://learn.vii.au01.mattr.global", "grant_type": "client_credentials" } ``` ### Responses #### 200 - Successful response ```json { "access_token": "s2dgbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6s2dcaEROemRDf5gbRVEwTTVSVFE0TmtZME9UZzVNVEpDTlVJNFJqRTBPREExTmpZMk1qazFPQSJ9", "expires_in": 14400, "token_type": "Bearer" } ``` #### 401 - Unauthorized # Retrieve tenant ## Endpoint ``` GET /v1/tenants/{tenantId} ``` Full URL: `https://manage.au01.mattr.global/v1/tenants/{tenantId}` ### Authorization Bearer token required. ## Description Retrieves a tenant by its ID. ### **Analytic events** * TENANT_RETRIEVE_START * TENANT_RETRIEVE_SUCCESS * TENANT_RETRIEVE_FAIL ### Path Parameters - `tenantId`: string (required) Unique ID of the tenant to retrieve. ### Responses #### 200 - Tenant retrieved ```json { "id": "86cb97a9-5e80-4ed7-af13-a170752bb1ea", "name": "My Tenant", "subdomain": "my-tenant.vii.au01.mattr.global", "environment": { "id": "fa605282-0223-4ae0-831d-af368bc39a55", "name": "MATTR Public Sydney, Australia", "domain": "vii.au01.mattr.global", "authorizationServerDomain": "manage.auth.auth0.com", "deploymentModel": "public", "region": { "id": "70bb433a-f0ec-4297-ad76-3b09c71311f3", "name": "AU01", "displayName": "Sydney, Australia" } }, "membership": { "roles": [ "dts-provider", "issuer" ] } } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Delete tenant ## Endpoint ``` DELETE /v1/tenants/{tenantId} ``` Full URL: `https://manage.au01.mattr.global/v1/tenants/{tenantId}` ### Authorization Bearer token required. ## Description Deletes a tenant by its ID. ### **Analytic events** * TENANT_DELETE_START * TENANT_DELETE_SUCCESS * TENANT_DELETE_FAIL ### Path Parameters - `tenantId`: string (required) Unique ID of the tenant to delete. ### Responses #### 204 - Tenant deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve clients ## Endpoint ``` GET /v1/tenants/{tenantId}/clients ``` Full URL: `https://manage.au01.mattr.global/v1/tenants/{tenantId}/clients` ### Authorization Bearer token required. ## Description Retrieves a list of clients authorized to interact with the requested tenant. ### **Analytic events** * TENANT_CLIENT_RETRIEVE_LIST_START * TENANT_CLIENT_RETRIEVE_LIST_SUCCESS * TENANT_CLIENT_RETRIEVE_LIST_FAIL ### Path Parameters - `tenantId`: string (required) Unique ID of the tenant to retrieve clients for. ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Clients retrieved ```json { "data": [ { "clientId": "suC7IhmDIawnlqBlEOuIqBWoqppcdI5", "name": "Example client", "permissions": [ "dids:read", "dids:create" ], "roles": [ "issuer" ] } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Create a client ## Endpoint ``` POST /v1/tenants/{tenantId}/clients ``` Full URL: `https://manage.au01.mattr.global/v1/tenants/{tenantId}/clients` ### Authorization Bearer token required. ## Description Creates a new client for the requested tenant. ### **Analytic events** * TENANT_CLIENT_CREATE_START * TENANT_CLIENT_CREATE_SUCCESS * TENANT_CLIENT_CREATE_FAIL ### Path Parameters - `tenantId`: string (required) Unique ID of the tenant to create a client for. ### Request Body ```json { "name": "Example client", "roles": [ "issuer" ] } ``` ### Responses #### 201 - Client created ```json { "clientId": "suC7IhmDIawnlqBlEOuIqBWoqppcdI5", "name": "Example client", "permissions": [ "dids:read", "dids:create" ], "roles": [ "issuer" ], "clientSecret": "QmtShBH3mkO05ra91dNO-YyPwPbfs1iokh57IgqhzVWTAZlolCdeAGYOG2kz1" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. #### 503 - Service Unavailable. The server is temporarily unavailable to handle requests. # Delete a client ## Endpoint ``` DELETE /v1/tenants/{tenantId}/clients/{clientId} ``` Full URL: `https://manage.au01.mattr.global/v1/tenants/{tenantId}/clients/{clientId}` ### Authorization Bearer token required. ## Description Deletes an existing client of the specified tenant. ### **Analytic events** * TENANT_CLIENT_DELETE_START * TENANT_CLIENT_DELETE_SUCCESS * TENANT_CLIENT_DELETE_FAIL ### Path Parameters - `tenantId`: string (required) Identifier of the tenant to delete the client from. - `clientId`: string (required) Identifier of the client to delete. ### Responses #### 204 - Client deleted #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. #### 503 - Service Unavailable. The server is temporarily unavailable to handle requests. # Invite a tenant member ## Endpoint ``` POST /v1/tenants/{tenantId}/invitations ``` Full URL: `https://manage.au01.mattr.global/v1/tenants/{tenantId}/invitations` ### Authorization Bearer token required. ## Description Invites a user to join the tenant and assigns roles to them within the tenant's context. If the user has not registered to the Self Service Portal yet, then they will receive an email with a link to accept the invite. If the user has already registered, then they will be added as a member to the tenant immediately. > **Note:** This endpoint can only be called by a Portal user that is a member of the tenant. Machine-to-machine (M2M) clients are not currently supported as inviters and will receive a `404 Resource Not Found` response, even when the client holds an `admin` role. This applies regardless of the role assigned, such as `admin`, `issuer`, or `dts-provider`. > > To invite members, use a Portal user that is a member of the target tenant. A reliable workflow is to create the tenant from the [MATTR Portal](/docs/platform-management/portal) rather than via an M2M client. The Portal user that creates the tenant becomes a member automatically, and can then invite additional users to it, either from the Portal UI or by calling this endpoint with the Portal user's access token. ### **Analytic events** * TENANT_MEMBER_INVITATION_CREATE_START * TENANT_MEMBER_INVITATION_CREATE_SUCCESS * TENANT_MEMBER_INVITATION_CREATE_FAIL ### Path Parameters - `tenantId`: string (required) Identifier of the tenant to add the member to. ### Request Body ```json { "email": "john-doe@example.com", "roles": [ "dts-provider", "issuer" ] } ``` ### Responses #### 200 - Member invited ```json { "userId": "8f6d40a9-d913-45e8-aa3e-8c99d62cd8fb", "status": "Active", "inviteExpiresAt": "2025-08-22T07:46:09.510Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 404 - Not Found. The specified resource was not found. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve tenant members ## Endpoint ``` GET /v1/tenants/{tenantId}/members ``` Full URL: `https://manage.au01.mattr.global/v1/tenants/{tenantId}/members` ### Authorization Bearer token required. ## Description Retrieves a list of all users that have access to the tenant. ### **Analytic events** * TENANT_MEMBER_RETRIEVE_LIST_START * TENANT_MEMBER_RETRIEVE_LIST_SUCCESS * TENANT_MEMBER_RETRIEVE_LIST_FAIL ### Path Parameters - `tenantId`: string (required) Identifier of the tenant to retrieve. ### Query Parameters - `limit`: number (default: 100) Range size of returned list. - `cursor`: string Starting point for the list of entries. ### Responses #### 200 - Tenant's members retrieved ```json { "data": [ { "id": "8f6d40a9-d913-45e8-aa3e-8c99d62cd8fb", "email": "john-doe@example.com", "name": "John Doe", "status": "Active", "roles": [ "dts-provider", "issuer" ], "permissions": [ "dids:read", "dids:create" ], "inviteExpiresAt": "2025-08-22T12:00:00.000Z" } ], "nextCursor": "Y3JlYXRlZEF0PTIwMjAtMDgtMjVUMDY6NDY6MDkuNTEwWiZpZD1hNjZmZmVhNS04NDhlLTQzOWQtODBhNC1kZGE1NWY1M2UzNmM" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 500 - Internal Server Error. An unexpected error occurred. # Retrieve a tenant member ## Endpoint ``` GET /v1/tenants/{tenantId}/members/{userId} ``` Full URL: `https://manage.au01.mattr.global/v1/tenants/{tenantId}/members/{userId}` ### Authorization Bearer token required. ## Description Retrieves an existing user that has access to the tenant. ### **Analytic events** * TENANT_MEMBER_RETRIEVE_START * TENANT_MEMBER_RETRIEVE_SUCCESS * TENANT_MEMBER_RETRIEVE_FAIL ### Path Parameters - `tenantId`: string (required) Identifier of the tenant. - `userId`: string (required) Identifier of the user. ### Responses #### 200 - Tenant member retrieved ```json { "id": "8f6d40a9-d913-45e8-aa3e-8c99d62cd8fb", "email": "john-doe@example.com", "name": "John Doe", "status": "Active", "roles": [ "dts-provider", "issuer" ], "permissions": [ "dids:read", "dids:create" ], "inviteExpiresAt": "2025-08-22T12:00:00.000Z" } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 500 - Internal Server Error. An unexpected error occurred. # Delete a tenant membership ## Endpoint ``` DELETE /v1/tenants/{tenantId}/memberships/{userId} ``` Full URL: `https://manage.au01.mattr.global/v1/tenants/{tenantId}/memberships/{userId}` ### Authorization Bearer token required. ## Description Removes the membership of a user from the specified tenant. This will remove all user permissions for this tenant. ### **Analytic events** * TENANT_MEMBERSHIP_DELETE_START * TENANT_MEMBERSHIP_DELETE_SUCCESS * TENANT_MEMBERSHIP_DELETE_FAIL ### Path Parameters - `tenantId`: string (required) Identifier of the tenant the user is being removed from. - `userId`: string (required) Identifier of the user being removed. ### Responses #### 204 - User removed from tenant #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 500 - Internal Server Error. An unexpected error occurred. # Update a tenant membership ## Endpoint ``` PUT /v1/tenants/{tenantId}/memberships/{userId} ``` Full URL: `https://manage.au01.mattr.global/v1/tenants/{tenantId}/memberships/{userId}` ### Authorization Bearer token required. ## Description Updates the membership of a user in the tenant. This includes the roles assigned to this user for this tenant. ### **Analytic events** * TENANT_MEMBERSHIP_UPDATE_START * TENANT_MEMBERSHIP_UPDATE_SUCCESS * TENANT_MEMBERSHIP_UPDATE_FAIL ### Path Parameters - `tenantId`: string (required) Identifier of the tenant. - `userId`: string (required) Identifier of the user who's membership is being updated. ### Request Body ```json { "roles": [ "dts-provider", "issuer" ] } ``` ### Responses #### 200 - Membership updated. ```json { "userId": "879a5524-d515-4aee-824a-c52fdcd4eea6", "tenantId": "8f49b206-e0bb-474d-8a4d-62186a9de886", "roles": [ "dts-provider", "issuer" ] } ``` #### 400 - Bad Request. The request was malformed or missing required parameters. ```json { "details": [ { "msg": "Invalid value", "param": "id", "location": "body" } ] } ``` #### 401 - Unauthorized. The client is not recognized by authorization server. #### 403 - Forbidden. The client is recognized by authorization server but is not allowed to access this resource. #### 500 - Internal Server Error. An unexpected error occurred.