Upgrade your app to use Link tokens



Overview

Introduction

Plaid is introducing a new link_token which replaces the static public_key. This is an improvement that provides better error validation, more advanced security, and enables us to surface Link event logs in the Plaid Dashboard.

This guide covers the client and server-side changes required to implement the new link_token. Future updates will be built using the link_tokens infrastructure, so we encourage you to upgrade today. Here's an overview of the updates before we dive into the details.

The Plaid flow still begins when your user wants to connect their bank account to your app.

Now before you open Link, create a link_token server-side and pass it to your app's client.

Use the temporary link_token to open Link. In the onSuccess callback, continue to send the public_token to your app's server.

Use the public_token to create a permanent access_token for the item.

Continue to store the access_token and use it to make product requests for your user's Item.


What's new

  • Link will now be initialized with a new link_token created by /link/token/create. This link_token will replace the public_key as means of authenticating the client.
  • Instead of using client-side configurations to initialize Link, you will pass these configurations to the /link/token/create endpoint and the resulting Link token will be used to initialize Link.
  • The INVALID_LINK_TOKEN error code is now available to gracefully handle invalidated link_tokens.
  • Link events from sessions created with the new link_token will be surfaced in the Logs section of the Dashboard. However, Link events from sessions created with the public_key will not.

You may be familiar with how to integrate with link_tokens if you've ever integrated with Link's old version of update mode that used the public_token. Using the link_token will be similar in that you'll be generating the token server-side and passing it to your app's client.

A link_token can be configured for different Link flows depending on the fields provided during token creation. It is the preferred way of initializing Link going forward. Refer to the Plaid docs for a full description of the /link/token/create endpoint and which Link flows it supports.

Please note that when using a link_token to initialize Link, you will need to pass in most of your Link configurations server-side in the /link/token/create endpoint rather than client-side where they previously existed. If these configs are passed in client-side when using the link_token, they will not have any affect on Link behavior.

Update your server

Add a new endpoint

Add a new authenticated endpoint to your app's server to create a link_token. When making the /link/token/create request, pass in the seven required parameters. This includes client_id, secret, client_name, products, country_codes, language, and user.client_user_id.

To learn more about the new user.client_user_id field and authenticating your endpoint, read below.

Create a link_token
POST /link/token/create
ParameterDescription
client_id
String, required
secret
String, required
client_name
String, required
Displayed once a user has successfully linked their Item.
language
String, required
Specify a Plaid-supported language to localize Link.

Supported languages:
  • English ( 'en' )
  • French ( 'fr' )
  • Spanish ( 'es' )
  • Dutch ( 'nl' )
country_codes
Array<String>, required
Specify an array of Plaid-supported country codes using the ISO-3166-1 alpha-2 country code standard. Note that if you initialize with a European country code, your users will see the European consent panel during the Link flow.
user
Object, required
An object containing information about your end user. See below for which fields are available.
products
Array<String>, optional
A list of Plaid product(s) you wish to use. Valid products are: transactions, auth, identity, income, assets, investments, liabilities, and payment_initiation. Only institutions that support all requested products will be shown. In Production, you will be billed for each product that you specify when initializing Link. If Link is launched with multiple country_codes, only products that you are enabled for in all countries will be used by Link.

Example: ['auth', 'transactions']
webhook
String, optional
Specify a webhook to associate with an Item. Plaid fires a webhook when the Item requires updated credentials, when new data is available, or when Auth numbers have been successfully verified.
link_customization_name
String, optional
Specify the name of a Link customization created in the Dashboard. The default customization is used if none is provided. Learn more
account_filters
Object, optional
Configures Link to return only accounts that are specified by these filters if Auth or Liabilities is a configured product. Must match the format:

{
  "<ACCOUNT_TYPE>": {
    "account_subtypes": ["<ACCOUNT_SUBTYPE>"]
  }
}
                      
Learn more
access_token
String, optional
An access_token associates your link_token with an existing Item. If you provide an access_token with no products configured, you will create a Link token enabled for Update mode.
redirect_uri
String, optional
A URI indicating the destination where a user should be forwarded after completing the Link flow. The redirect_uri is used to support OAuth authentication flows when launching Link in the browser or via a webview.
payment_initiation
Object, optional
An object containing configs specific to the payment_initiation product. If this config is provided, you should also include the payment_initiation product in the products array to enable the link_token for Payment Initiation

See below for payment_initiation fields.
KeyDescription
client_user_id
String, required
A unique identifier for each of your users. Do not use personally identifiable information such as an email or phone number.

Using user.client_user_id will allow for easier debugging in the Dashboard logs. You will be able to search for Link events that belong to one of your end users.
KeyDescription
payment_id
String, required
The payment_id returned from the /payment_initiation/payment/create request.

To learn more about link_tokens and how to configure them for specific Link flows like Update Mode or Payment Initiation (UK), visit the main docs.

/link/token/create response

Authenticate your endpoint

We require that the endpoint used to create a link_token only be available to users that are logged in to your app. Once your user is logged in, pass an identifier that uniquely identifies your user into the user.client_user_id field. The value of this id should not be personally identifiable information like an email or phone number.

Using user.client_user_id will allow for easier debugging in the Dashboard logs. You will be able to search for Link logs that belong to one of your end users.

Update your client

For each of your web and mobile apps, the first step is to use the new endpoint you created to fetch a link_token. Once you've created one, pass it into one of Plaid's Link SDKs to initialize Link. You can then safely remove the public_key and other client-side configs that should now be configured in the /link/token/create request like client_name and products.

If the token expires or the user enters too many invalid credentials, the link_token can become invalidated. If it does get into an invalid state, Link will exit with an INVALID_LINK_TOKEN error code. By recognizing when this error occurs in the onExit callback, you can generate a fresh link_token for the next time your user opens Link.

To handle an invalid link_token for Link in the browser, you will need to gracefully clean up the old iframe before reinitializing Link. To do this, use the destroy() method and reinitialize Link with a new link_token in the onExit callback.

Initialize Link with a Link Token (Web)

The iOS SDK now provides an initWithLinkToken method on both the PLKConfiguration and the PLKPlaidLinkViewController classes that should allow you to easily initialize Link with a link_token.

Below, we show how to initialize Link with the link_token in iOS. For more in depth coverage on how to integrate with Link iOS, visit the iOS docs.

Initialize Link with a Link Token (iOS)

let linkConfiguration = PLKConfiguration(linkToken: "GENERATED_LINK_TOKEN")
let linkViewDelegate = self
let linkViewController = PLKPlaidLinkViewController(
  linkToken: "GENERATED_LINK_TOKEN",
  configuration: linkConfiguration,
  delegate: linkViewDelegate,
)
if (UI_USER_INTERFACE_IDIOM() == .pad) {
    linkViewController.modalPresentationStyle = .formSheet;
}
present(linkViewController, animated: true)

let linkConfiguration = PLKConfiguration(linkToken: "GENERATED_LINK_TOKEN")
let linkViewDelegate = self
let linkViewController = PLKPlaidLinkViewController(
  linkToken: "GENERATED_LINK_TOKEN",
  configuration: linkConfiguration,
  delegate: linkViewDelegate,
)
if (UI_USER_INTERFACE_IDIOM() == .pad) {
    linkViewController.modalPresentationStyle = .formSheet;
}
present(linkViewController, animated: true)

let linkConfiguration = PLKConfiguration(linkToken: "GENERATED_LINK_TOKEN")
let linkViewDelegate = self
let linkViewController = PLKPlaidLinkViewController(
  linkToken: "GENERATED_LINK_TOKEN",
  configuration: linkConfiguration,
  delegate: linkViewDelegate,
)
if (UI_USER_INTERFACE_IDIOM() == .pad) {
    linkViewController.modalPresentationStyle = .formSheet;
}
present(linkViewController, animated: true)

let linkConfiguration = PLKConfiguration(linkToken: "GENERATED_LINK_TOKEN")
let linkViewDelegate = self
let linkViewController = PLKPlaidLinkViewController(
  linkToken: "GENERATED_LINK_TOKEN",
  configuration: linkConfiguration,
  delegate: linkViewDelegate,
)
if (UI_USER_INTERFACE_IDIOM() == .pad) {
    linkViewController.modalPresentationStyle = .formSheet;
}
present(linkViewController, animated: true)

let linkConfiguration = PLKConfiguration(linkToken: "GENERATED_LINK_TOKEN")
let linkViewDelegate = self
let linkViewController = PLKPlaidLinkViewController(
  linkToken: "GENERATED_LINK_TOKEN",
  configuration: linkConfiguration,
  delegate: linkViewDelegate,
)
if (UI_USER_INTERFACE_IDIOM() == .pad) {
    linkViewController.modalPresentationStyle = .formSheet;
}
present(linkViewController, animated: true)

let linkConfiguration = PLKConfiguration(linkToken: "GENERATED_LINK_TOKEN")
let linkViewDelegate = self
let linkViewController = PLKPlaidLinkViewController(
  linkToken: "GENERATED_LINK_TOKEN",
  configuration: linkConfiguration,
  delegate: linkViewDelegate,
)
if (UI_USER_INTERFACE_IDIOM() == .pad) {
    linkViewController.modalPresentationStyle = .formSheet;
}
present(linkViewController, animated: true)

PLKConfiguration* linkConfiguration;
@try {
  linkConfiguration = [[PLKConfiguration alloc] initWithLinkToken:@"GENERATED_LINK_TOKEN"];
  id<PLKPlaidLinkViewDelegate> linkViewDelegate  = self;
  PLKPlaidLinkViewController* linkViewController = [[PLKPlaidLinkViewController alloc] initWithLinkToken:@"GENERATED_LINK_TOKEN" configuration:linkConfiguration delegate:linkViewDelegate];
  if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
      linkViewController.modalPresentationStyle = UIModalPresentationFormSheet;
  }
  [self presentViewController:linkViewController animated:YES completion:nil];
} @catch (NSException *exception) {
  NSLog(@"Invalid configuration: %@", exception);
}
  

let linkConfiguration = PLKConfiguration(linkToken: "GENERATED_LINK_TOKEN")
let linkViewDelegate = self
let linkViewController = PLKPlaidLinkViewController(
  linkToken: "GENERATED_LINK_TOKEN",
  configuration: linkConfiguration,
  delegate: linkViewDelegate,
)
if (UI_USER_INTERFACE_IDIOM() == .pad) {
    linkViewController.modalPresentationStyle = .formSheet;
}
present(linkViewController, animated: true)

The Android SDK exposes a new class called LinkTokenConfiguration. This class accepts the link_token and should be passed into the openPlaidLink method. Note that it does not accept a lot of existing parameters like client_name or products because these should be configured in the /link/token/create request instead.

Below, we show how to use the LinkTokenConfiguration class to open Link. For more in depth coverage on the Android SDK, visit the Android docs.

Initialize Link with a Link Token (Android)


import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

import com.plaid.link.Plaid
import com.plaid.link.linkTokenConfiguration
import com.plaid.link.openPlaidLink
import com.plaid.link.configuration.AccountSubtype
import com.plaid.link.configuration.LinkLogLevel
import com.plaid.link.configuration.PlaidEnvironment
import com.plaid.link.configuration.PlaidProduct
import com.plaid.link.event.LinkEvent
import java.util.Locale

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Optional
    Plaid.setLinkEventListener { event -> Log.i("Event", event.toString()) }

    // Open Link – put this inside of a Button / Fab click listener
    this@MainActivity.openPlaidLink(
      linkTokenConfiguration {
        // required
        token = "GENERATED_LINK_TOKEN"

        // optional
        logLevel = LinkLogLevel.WARN // Defaults to ASSERT
        extraParams = mapOf() // Map of additional configs
      }
    );
  }
}


import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

import com.plaid.link.Plaid
import com.plaid.link.linkTokenConfiguration
import com.plaid.link.openPlaidLink
import com.plaid.link.configuration.AccountSubtype
import com.plaid.link.configuration.LinkLogLevel
import com.plaid.link.configuration.PlaidEnvironment
import com.plaid.link.configuration.PlaidProduct
import com.plaid.link.event.LinkEvent
import java.util.Locale

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Optional
    Plaid.setLinkEventListener { event -> Log.i("Event", event.toString()) }

    // Open Link – put this inside of a Button / Fab click listener
    this@MainActivity.openPlaidLink(
      linkTokenConfiguration {
        // required
        token = "GENERATED_LINK_TOKEN"

        // optional
        logLevel = LinkLogLevel.WARN // Defaults to ASSERT
        extraParams = mapOf() // Map of additional configs
      }
    );
  }
}


import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

import com.plaid.link.Plaid
import com.plaid.link.linkTokenConfiguration
import com.plaid.link.openPlaidLink
import com.plaid.link.configuration.AccountSubtype
import com.plaid.link.configuration.LinkLogLevel
import com.plaid.link.configuration.PlaidEnvironment
import com.plaid.link.configuration.PlaidProduct
import com.plaid.link.event.LinkEvent
import java.util.Locale

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Optional
    Plaid.setLinkEventListener { event -> Log.i("Event", event.toString()) }

    // Open Link – put this inside of a Button / Fab click listener
    this@MainActivity.openPlaidLink(
      linkTokenConfiguration {
        // required
        token = "GENERATED_LINK_TOKEN"

        // optional
        logLevel = LinkLogLevel.WARN // Defaults to ASSERT
        extraParams = mapOf() // Map of additional configs
      }
    );
  }
}


import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

import com.plaid.link.Plaid
import com.plaid.link.linkTokenConfiguration
import com.plaid.link.openPlaidLink
import com.plaid.link.configuration.AccountSubtype
import com.plaid.link.configuration.LinkLogLevel
import com.plaid.link.configuration.PlaidEnvironment
import com.plaid.link.configuration.PlaidProduct
import com.plaid.link.event.LinkEvent
import java.util.Locale

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Optional
    Plaid.setLinkEventListener { event -> Log.i("Event", event.toString()) }

    // Open Link – put this inside of a Button / Fab click listener
    this@MainActivity.openPlaidLink(
      linkTokenConfiguration {
        // required
        token = "GENERATED_LINK_TOKEN"

        // optional
        logLevel = LinkLogLevel.WARN // Defaults to ASSERT
        extraParams = mapOf() // Map of additional configs
      }
    );
  }
}

import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;

import com.plaid.link.Plaid
import com.plaid.link.openPlaidLink;
import com.plaid.link.configuration.AccountSubtype;
import com.plaid.link.configuration.LinkTokenConfiguration;
import com.plaid.link.configuration.LinkLogLevel;
import com.plaid.link.configuration.PlaidEnvironment;
import com.plaid.link.configuration.PlaidProduct;
import com.plaid.link.event.LinkEvent;
import java.util.Locale;
import kotlin.Unit;

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Optional
    Plaid.setLinkEventListener(linkEvent -> {
      Log.i("Event", linkEvent.toString());
      return Unit.INSTANCE;
    });

    // Open Link – put this inside of a Button / Fab click listener
    Plaid.openLink(
        this,
        new LinkTokenConfiguration.Builder()
            .token("GENERATED_LINK_TOKEN")
            .logLevel(LogLevel.WARN) // Defaults to ASSERT
            .build()
            .toLinkConfiguration()
    );
  }
}


import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

import com.plaid.link.Plaid
import com.plaid.link.linkTokenConfiguration
import com.plaid.link.openPlaidLink
import com.plaid.link.configuration.AccountSubtype
import com.plaid.link.configuration.LinkLogLevel
import com.plaid.link.configuration.PlaidEnvironment
import com.plaid.link.configuration.PlaidProduct
import com.plaid.link.event.LinkEvent
import java.util.Locale

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Optional
    Plaid.setLinkEventListener { event -> Log.i("Event", event.toString()) }

    // Open Link – put this inside of a Button / Fab click listener
    this@MainActivity.openPlaidLink(
      linkTokenConfiguration {
        // required
        token = "GENERATED_LINK_TOKEN"

        // optional
        logLevel = LinkLogLevel.WARN // Defaults to ASSERT
        extraParams = mapOf() // Map of additional configs
      }
    );
  }
}


import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

import com.plaid.link.Plaid
import com.plaid.link.linkTokenConfiguration
import com.plaid.link.openPlaidLink
import com.plaid.link.configuration.AccountSubtype
import com.plaid.link.configuration.LinkLogLevel
import com.plaid.link.configuration.PlaidEnvironment
import com.plaid.link.configuration.PlaidProduct
import com.plaid.link.event.LinkEvent
import java.util.Locale

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Optional
    Plaid.setLinkEventListener { event -> Log.i("Event", event.toString()) }

    // Open Link – put this inside of a Button / Fab click listener
    this@MainActivity.openPlaidLink(
      linkTokenConfiguration {
        // required
        token = "GENERATED_LINK_TOKEN"

        // optional
        logLevel = LinkLogLevel.WARN // Defaults to ASSERT
        extraParams = mapOf() // Map of additional configs
      }
    );
  }
}


import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity

import com.plaid.link.Plaid
import com.plaid.link.linkTokenConfiguration
import com.plaid.link.openPlaidLink
import com.plaid.link.configuration.AccountSubtype
import com.plaid.link.configuration.LinkLogLevel
import com.plaid.link.configuration.PlaidEnvironment
import com.plaid.link.configuration.PlaidProduct
import com.plaid.link.event.LinkEvent
import java.util.Locale

class MainActivity : AppCompatActivity() {

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Optional
    Plaid.setLinkEventListener { event -> Log.i("Event", event.toString()) }

    // Open Link – put this inside of a Button / Fab click listener
    this@MainActivity.openPlaidLink(
      linkTokenConfiguration {
        // required
        token = "GENERATED_LINK_TOKEN"

        // optional
        logLevel = LinkLogLevel.WARN // Defaults to ASSERT
        extraParams = mapOf() // Map of additional configs
      }
    );
  }
}

As this update involves an additional API call when adding an Item, create a link_token when your user initially visits your app to avoid adding latency to your Link flow.

Verify your changes

Once you have updated both your app's client and server, it's time to test that your integration works. The best way to test is by using the test credentials in the Sandbox.


username: user_good
password: pass_good

Additionally, test your error handling flow for INVALID_LINK_TOKEN by using the Sandbox test credentials to force an error.


username: user_custom
password: { "force_error": "INVALID_LINK_TOKEN" }

As a reminder, you can also verify that you have updated correctly by viewing Link event logs in the Plaid Dashboard.

Update API Endpoints

In order to completely migrate off of the public_key, there are a few Plaid API endpoints that should replace the public_key with the client_id and secret. These are /institutions/search, /institutions/get_by_id, and /sandbox/public_token/create.

Note that because we now use the client_id and secret to authenticate these endpoints, you should only call these endpoints from your server. For those calling these endpoints from the Plaid API client libraries, a major version update has been released that removes the public_key as an initialization argument.

Disable the Public Key

After completing all of the above steps, you can now confidently disable the use of the public_key in Link and the API via the Plaid Dashboard. You will be able to choose which environments to disable the public_key so this can be done incrementally for Sandbox, Development, and Production.

If you disable the public_key for Link, you will no longer be able to use the public_key to initialize Link. Similarly, if you disable the public_key for the API, you will only be able to use the client_id and secret for the above API requests. At this point, you will have successfully migrated off of the public_key.

Congratulations on upgrading to the new link_token! If you run into any issues with the link_token integration, please file a support ticket in the Plaid Dashboard.