light-mode-image
Learn
Management & Operations

SDK Tethering

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 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

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

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

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:

Request
POST /v1/holder/applications
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 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:

Response
{
    "id": "1ef1f867-20b4-48ea-aec1-bea7aff4964c", 
    "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:

Request
POST /v1/holder/applications
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 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 for more details.

A successful response returns a 201 status code with the created Holder Application:

Response
{
    "id": "a82bfa46-72a0-4cde-b6cb-2a0de7e2f3c4", 
    "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:

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

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:

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:

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), your initialization code must pass the correct platform-specific applicationId at runtime. Use Platform.OS to select the appropriate value:

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

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

To view all registered instances for a Holder Application and track usage:

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:

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

To remove a specific registered instance:

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

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

ScenarioRecommended setting
Production apps in distributionrequired: true — Provides the strongest integrity guarantees by verifying the app and device through OS-level attestation.
Development and testingrequired: false — Useful when running on simulators or devices where attestation services are unavailable.
Broad device compatibilityrequired: 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)

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.

How would you rate this page?

Last updated on

On this page