Skip to main content
By the end of this guide, you’ll know how to use Mesh Managed Tokens (MMT) to create a seamless return-user experience — so users who’ve already connected an account don’t need to re-authenticate.
Before you start

Overview

Mesh’s Managed Token system (MMT) simplifies how you manage user authentication tokens. MMT securely stores and manages token lifecycles on your behalf, giving you a persistent tokenId for each connected user account. You can use this to create a seamless return-user experience for exchanges and to interact with certain Mesh APIs — no manual token refresh or re-authentication required.

Benefits

  • Streamlined UX: After connecting their exchange account to your app, users can skip repetitive authentication steps on future visits.
  • Persistent Access: Mesh handles all token refresh logic. For exchanges with long-lived tokens (like Coinbase), access stays valid indefinitely. For those with expiring tokens (like Binance), users will be prompted to re-authenticate when needed — but the tokenId you store never changes, so no backend updates are required on your part.
  • Integration-Agnostic: Works across different auth methods (OAuth, credentials-based) with no added client-side complexity.

How to Implement MMT

1. Obtain a tokenId

When a user authenticates with their exchange or wallet account, you will receive the SDK onIntegrationConnected event, which contains an object like this:
{
    "accessToken": {
        "accountTokens": [
            {
                "account": {
                    "meshAccountId": "<meshAccountId>",
                    "frontAccountId": "<frontAccountId>",
                    "accountId": "<accountId>",
                    "accountName": "<accountName>",
                    "fund": 0,
                    "cash": 0
                },
                "accessToken": "<accessToken>",
                "tokenId": "<tokenId>" // Use this
            }
        ],
        "brokerType": "<brokerType>", // Use this
        "brokerName": "<brokerName>",
        "brokerBrandInfo": {}
    }
}
Capture the tokenId and brokerType.

2. Build the accessTokens object

See the Use Mesh’s callback functions guide for more information on how to use the onIntegrationConnected callback function to do this. This will later be passed into the Mesh SDK for a returning user experience.

Notes & references:

  • Store the tokenId securely, for example in a database. Make sure each tokenId and brokerType pair is clearly tied to the userId that connected the account.
  • You’ll notice that the tokenId & accessToken in the integrationConnected event are the same for the initial connection of a particular user account. But if the accessToken is ever refreshed or replaced, they will diverge. Link facilitates the user experience, and Mesh manages the token refresh logic for you, which means the tokenId will remain unchanged for a given userId + brokerType combination. This means you should put the tokenId value from the SDK event into the accessToken field in this object.
  • The brokerName, accountId, and accountName fields are obsolete but must still be passed (can be empty) when initializing the SDK.

3. Pass the accessTokens object into the Mesh SDK

const accessTokens: IntegrationAccessToken[] = [
  {
    accessToken: '<accessToken>', // Put the tokenId here
    brokerType: '<brokerType>', // Put the brokerType here
    brokerName: '',
    accountId: '',
    accountName: ''
  }
]
This will then be used when initializing future sessions of the Mesh SDK for this user so they can skip re-authentication and proceed directly into the transfer or portfolio flow.
const connection = createLink({
  renderType: 'embedded',
  theme: 'system',
  language: 'system',
  displayFiatCurrency: 'USD'
  accessTokens: accessTokens, // Refers to the accessTokens object
  onIntegrationConnected: payload => {
    // Payload contains accessTokens for newly connected accounts
    console.debug('[MESH LINK integration connected]', payload)
  },
})

connection.openLink(linkToken, 'custom-iframe-id')
let accessTokens = [
    IntegrationAccessToken(
        accountId: "",
        accountName: "",
        accessToken: "<accessToken>", // Put the tokenId here
        brokerType: "<brokerType>", // Put the brokerType here
        brokerName: ""
    )
]
This will then be used when initializing future sessions of the Mesh SDK for this user so they can skip re-authentication and proceed directly into the transfer or portfolio flow.
func launchMeshLink(
    linkToken: String,
    viewController: UIViewController,
    accessTokens: [IntegrationAccessToken]? = nil
) {
    var linkHandler: LinkHandler?

    let settings = LinkSettings(
        accessTokens: accessTokens, // Refers to the accessTokens object
        language: "system",
        displayFiatCurrency: "USD",
        theme: .system
    )

    // Called after a user connects an exchange, wallet, or other integration.
    // Payload contains accessTokens for newly connected accounts
    let onIntegrationConnected: (LinkPayload) -> Void = { linkPayload in
        switch linkPayload {
        case .accessToken(let accessTokenPayload):
            print(accessTokenPayload)
        case .delayedAuth(let delayedAuthPayload):
            print(delayedAuthPayload)
        @unknown default:
            print("unknown LinkPayload")
        }
    }

    let configuration = LinkConfiguration(
        linkToken: linkToken,
        settings: settings,
        disableDomainWhiteList: false,
        onIntegrationConnected: onIntegrationConnected,
    )

    let result = configuration.createHandler()

    // createHandler validates the configuration before Link is presented.
    switch result {
    case .success(let handler):
        linkHandler = handler
        // Present Link from the current UIViewController.
        handler.present(in: viewController)
    case .failure(let error):
        print(error)
    @unknown default:
        print("unknown LinkResult")
    }
}
val accessTokens = listOf(
    IntegrationAccessToken(
        accountId = "",
        accountName = "",
        accessToken = "<accessToken>", // Put the tokenId here
        brokerType = "<brokerType>", // Put the brokerType here
        brokerName = "",
    ),
)
This will then be used when initializing future sessions of the Mesh SDK for this user so they can skip re-authentication and proceed directly into the transfer or portfolio flow.
// Register the Link launcher as an Activity or Fragment property
private val linkLauncher = registerForActivityResult(LaunchLink()) { result ->
    when (result) {
        is LinkSuccess -> {
            // Called when Link returns payloads, such as a connected account or completed transfer.
            // Upon successful integration payload contains accessTokens for newly connected accounts
            Log.i(LOG_TAG, "LinkSuccess: ${result.payloads}")
        }
        is LinkExit -> {
            // Called when Link exits without payloads, including user exits or errors.
            Log.i(LOG_TAG, "LinkExit: ${result.errorMessage}")
        }
    }
}

private fun launchMeshLink(linkToken: String, accessTokens: List<IntegrationAccessToken>? = null) {
    // Create LinkConfiguration
    val configuration = LinkConfiguration(
        token = linkToken,
        theme = LinkTheme.SYSTEM,
        language = "system",
        displayFiatCurrency = "USD",
        accessTokens = accessTokens, // Refers to the accessTokens object
    )

    // Launch
    linkLauncher.launch(configuration)
}
const accessTokens: IntegrationAccessToken[] = [
  {
    accessToken: '<accessToken>', // Put the tokenId here
    brokerType: '<brokerType>', // Put the brokerType here
    brokerName: '',
    accountId: '',
    accountName: ''
  }
]
This will then be used when initializing future sessions of the Mesh SDK for this user so they can skip re-authentication and proceed directly into the transfer or portfolio flow.
return (
  <LinkConnect
    linkToken={linkToken}
    settings={{
      language: 'system',
      displayFiatCurrency: 'USD',
      accessTokens, // Refers to the accessTokens object
    }}
    onIntegrationConnected={(payload: LinkPayload) => {
      // Payload contains accessTokens for newly connected accounts
      console.log('[MESH LINK integration connected]', payload);
    }}
  />
);
final accessTokens = [
  IntegrationAccessToken(
      accessToken: '<accessToken>', // Put the tokenId here
      brokerType: '<brokerType>', // Put the brokerType here
      brokerName: '',
      accountId: '',
      accountName: '',
    )
];
This will then be used when initializing future sessions of the Mesh SDK for this user so they can skip re-authentication and proceed directly into the transfer or portfolio flow.
Future<void> _showMeshLinkPage(
    String linkToken, {
    List<IntegrationAccessToken> accessTokens = const [],
}) async {
  // Show MeshSdk
  final result = await MeshSdk.show(
    context,
    configuration: MeshConfiguration(
      language: 'system',
      displayFiatCurrency: 'USD',
      theme: ThemeMode.system,
      integrationAccessTokens: accessTokens, // Refers to the accessTokens object
      linkToken: linkToken,
      onIntegrationConnected: (integration) {
        // Called after a user connects an exchange, wallet, or other integration.
        // Payload contains accessTokens for newly connected accounts
        print('Integration connected: $integration');
      },
    ),
  );

  // Handle the result
  switch (result) {
    case MeshSuccess():
      print('Mesh link finished successfully');
    case MeshError():
      print('Mesh link error: ${result.type}');
  }

  // Alternatively, use `when` method
  result.when(
    success: (success) {
      final payload = success.payload;
      print('Mesh link success: ${payload.page}');
    },
    error: (error) {
      final errorType = error.type;
      print('Mesh link error: $errorType');
    },
  );
}

Token Lifecycle and Behavior

ScenarioResult
Same user reconnects with same integrationReturns same tokenId
Different user connects to same integrationReturns a new tokenId
Same user connects with different scopes (e.g. read vs. write)Returns distinct tokenId per scope
Write endpoint called using read-only TokenIdAPI returns a scope mismatch error
Token revoked by client (use Remove connection endpoint)Associated access is permanently disabled and Mesh also deletes the token physically, without any way to restore it

Supported Integrations

This list is growing and will be updated over time. As new integrations are added, you will not have to update support on your end.
  • Coinbase
  • Binance
  • Uphold
  • Note: connections with self-custody wallets are maintained on subsequent sessions (unless the user actively kills the connection in their wallet app) without the need for handling tokens. This means the same smooth return user journey is achieved for wallet transactions.

Security Considerations

MMT streamlines token handling and unlocks some powerful functionality — here’s how Mesh protects the data under the hood:
  • Encrypted Storage: All tokens are encrypted at rest using modern encryption standards.
  • Scoped Access: Each tokenId is tied to the permission scope (read or write) associated with the API key. Unauthorized operations (e.g., calling a write endpoint with a read-scoped token) will be rejected.
  • Client-Level Isolation: Each tokenId is also scoped to a specific clientId. Even if the same end user connects the same integration account across multiple client apps, the tokens are isolated and not shared across clients.
  • User-Level Isolation: Each tokenId is unique to a specific endUserId and integration.
  • Token Revocation: Clients can revoke a tokenId, permanently disabling access and triggering a secure deletion process. There is no path to restore a revoked token.

What’s next

Next up: Polish the experience — with the core integration working, this guide walks you through the finishing touches: a well-designed entry point, clean session handling, and webhook setup.
AI coding reference — a compact summary of this page’s APIs, parameters, and patterns for use by AI coding assistants (following the llms.txt standard). Human readers can safely ignore this.llms.txt — Supercharge return-users (Mesh Managed Tokens)Use Mesh Managed Tokens (MMT) for one-tap return-user flows. Mesh manages token refresh; you store a stable tokenId.Step 1 — Capture from onIntegrationConnected: Extract accessToken.accountTokens[].tokenId and accessToken.brokerType. Store server-side per userId + brokerType.Step 2 — Build accessTokens array: [{ accessToken: '<tokenId>', brokerType: '<brokerType>', brokerName: '', accountId: '', accountName: '' }]Critical: put tokenId in the accessToken field (not the raw accessToken string). tokenId is stable even when the underlying token rotates.Step 3 — Pass into SDK on next session: Include accessTokens array in createLink() / LinkSettings / LinkConfiguration / MeshConfiguration.Token lifecycle: Same user + integration → same tokenId | Different user or scope → new tokenId | Revoked → permanently disabled, unrestorable | Coinbase: tokens valid indefinitely | Binance: tokens expire, user re-auths but tokenId unchanged.Supported integrations: Coinbase, Binance, Uphold (growing). Self-custody wallet connections persist without tokens.Security: Tokens encrypted at rest; scoped per clientId, endUserId, and permission scope (read vs. write).