Plaid logo
Docs
ALL DOCS

Transfer (beta)

  • Introduction to Transfer and Transfer UI
  • Sending or receiving funds
  • Sweeping funds
  • Reconciling transfers
  • ACH processing windows
  • ACH returns
  • ACH authorization
  • Transfers using Transfer UI
  • Transfer webhooks
Plaid logo
Docs
Plaid.com
Get API keys

Sending or Receiving Funds Using Transfer UI

Facilitate transfers with an intuitive user interface

Overview

Transfer UI requires that both the sender and recipient reside within the United States. In addition, it is only supported in Plaid's Sandbox and Production environments.

Plaid Transfer UI is a user interface that makes it easy for end users to authorize transfers. With Transfer UI, users are able to authorize two types of transfers: payments or disbursements. Before authorizing a transfer, users are able to review transfer details, such as amount, fund origination account, fund target account, and more.

Transfer UI also provides the same series of return risk checks that are provided by a direct API integration with the Transfer API. These checks analyze proposed transfers and inform users of any potential problems with a transfer. When a potential return is detected, users can remediate the problem within the UI.

There are two endpoints that are specific to Transfer UI:

  • /transfer/intent/create
  • /transfer/intent/get

This guide will demonstrate how to use these endpoints to invoke Transfer UI and retrieve information about transfer intents.

Prerequisites for using Transfer UI

Migrate to Account Select v2

To use Transfer UI, your Plaid integration must support Account Select v2 ("ASv2"). In the Plaid Dashboard, create a new customization for Transfer UI with the view behavior set to "Enabled for one account". This is the only view behavior supported by Transfer UI. To detect additional accounts for Items created or updated with ASv2, listen for the NEW_ACCOUNTS_AVAILABLE webhook.

For more information on how to implement ASv2, see Account Select v2 Migration Guide.

Generating a transfer intent object

To use Transfer UI, call the /transfer/intent/create endpoint. This endpoint invokes the Transfer UI and generates a transfer intent object. Take special note of the id field returned in the transfer intent object. The value of this field is Plaid's unique identifier for the transfer intent object and can be used later to track the status of a transfer intent.

1const request: TransferIntentCreateRequest = {
2 account_id: '3gE5gnRzNyfXpBK5wEEKcymJ5albGVUqg77gr',
3 mode: 'PAYMENT'
4 amount: '12.34',
5 description: 'Foobar',
6 ach_class: 'ppd',
7 user: {
8 legal_name: 'Leslie Knope',
9 }
10};
11
12try {
13 const response = await client.transferIntentCreate(request);
14} catch (error) {
15 // handle error
16}
1{
2 "transfer_intent": {
3 "account_id": "3gE5gnRzNyfXpBK5wEEKcymJ5albGVUqg77gr",
4 "ach_class": "ppd",
5 "amount": "12.34",
6 "iso_currency_code": "USD",
7 "created": "2020-08-06T17:27:15Z",
8 "description": "Foobar",
9 "id": "460cbe92-2dcc-8eae-5ad6-b37d0ec90fd9",
10 "metadata": {
11 "key1": "value1",
12 "key2": "value2"
13 },
14 "mode": "PAYMENT",
15 "origination_account_id": "9853defc-e703-463d-86b1-dc0607a45359",
16 "status": "PENDING",
17 "user": {
18 "address": {
19 "street": "100 Market Street",
20 "city": "San Francisco",
21 "region": "CA",
22 "postal_code": "94103",
23 "country": "US"
24 },
25 "email_address": "lknope@email.com",
26 "legal_name": "Leslie Knope",
27 "phone_number": "123-456-7890"
28 }
29 },
30 "request_id": "saKrIBuEB9qJZno"
31}

Generating a Link token with the transfer intent ID

New Items

To use Transfer UI, an Item will need to first be created if the end user has not previously linked an Item.

Generate a Link token by calling /link/token/create and specify the following:

  • "transfer", in the products array. Ensure that "transfer" is the only product being initialized.

  • A transfer object in the body of the create request. Within the transfer object, specify the transfer intent ID using the value of id returned by /transfer/intent/create.

  • The name of your Link customization for Transfer UI (for ASv2).

1const request: LinkTokenCreateRequest = {
2 user: {
3 client_user_id: 'user-id',
4 },
5 client_name: 'Plaid App',
6 products: ['transfer'],
7 country_codes: ['US'],
8 language: 'en',
9 webhook: 'https://sample-web-hook.com',
10 redirect_uri: 'https://domainname.com/oauth-page.html',
11 transfer: {
12 intent_id: 'TRANSFER_INTENT_ID',
13 },
14 account_filters: {
15 depository: {
16 account_subtypes: [
17 'DepositoryAccountSubtype.Checking, DepositoryAccountSubtype.Savings',
18 ],
19 },
20 },
21 link_customization_name: 'single-account-select-transfer-ui',
22};
23try {
24 const response = await plaidClient.linkTokenCreate(request);
25 const linkToken = response.data.link_token;
26} catch (error) {
27 // handle error
28}
Existing Items

If the end user has previously linked an account, they may already have an existing Item. For existing Items, you don't need to generate a new Link token. Instead, call /link/token/create and specify the following:

  • The existing access token for the Item.

  • "transfer", in the products array. Ensure that "transfer" is the only product being initialized.

  • A transfer object in the body of the create request. Within the transfer object, specify the transfer intent ID using the value of id returned by /transfer/intent/create.

  • The name of your Link customization for Transfer UI (for ASv2).

1const request: LinkTokenCreateRequest = {
2 user: {
3 client_user_id: 'user-id',
4 },
5 client_name: 'Plaid App',
6 products: ['transfer'],
7 country_codes: ['US'],
8 language: 'en',
9 webhook: 'https://sample-web-hook.com',
10 redirect_uri: 'https://domainname.com/oauth-page.html',
11 transfer: {
12 intent_id: 'TRANSFER_INTENT_ID',
13 },
14 account_filters: {
15 depository: {
16 account_subtypes: [
17 'DepositoryAccountSubtype.Checking, DepositoryAccountSubtype.Savings',
18 ],
19 },
20 },
21 link_customization_name: 'single-account-select-transfer-ui',
22 access_token: 'EXISTING_ACCESS_TOKEN',
23};
24try {
25 const response = await plaidClient.linkTokenCreate(request);
26 const linkToken = response.data.link_token;
27} catch (error) {
28 // handle error
29}

Initializing Link with the generated token

Next, initialize Link using the Link token you generated, and then open Link. If a new Item is being created, end users will first be prompted to link their bank account via Link before they can authorize and review the transfer. For existing Items (where an access token has been stored for the associated Item), end users will bypass the account linking flow in Link and immediately be able to authorize and review the proposed transfer.

When integrating Transfer UI, it's best to store access tokens associated with Items so that you can use them when invoking subsequent transfer requests. This will provide end users with an expedited Transfer UI experience by allowing them to bypass the account linking flow in Link so that they can quickly authorize and review transfers.

For more information on initializing Link, see Plaid Link: Web.

1const handler = Plaid.create({
2 token: 'GENERATED_LINK_TOKEN',
3 onSuccess: (public_token, metadata) => {},
4 onLoad: () => {},
5 onExit: (err, metadata) => {},
6 onEvent: (eventName, metadata) => {},
7 receivedRedirectUri: null,
8});
9
10// Open Link
11handler.open();

Tracking transfer creation

The instructions in this section correspond to the Web and Webview libraries for Link. If you're using a mobile SDK, information about the transer intent status can be found in the metadataJson field in the SDK's onSuccess callback. As an example, see Android: metadataJson.

You can determine whether a transfer was successfully created by referring to the transfer_status field in the metadata object returned by onSuccess. A value of complete indicates that the transfer was successfully originated. A value of incomplete indicates that the transfer was not originated. Note that this field only indicates the status of a transfer creation. It does not indicate the status of a transfer (i.e., funds movement). For more information on transfer intents, see Retrieving additional information about transfer intents. For help troubleshooting incomplete transfers, see Troubleshooting transfers.

In addition, when using Transfer UI, the onSuccess callback is called at a different point in time in the Link flow. Typically, the onSuccess callback is called after an account is successfully linked using Link. When using Transfer UI, however, the onSuccess callback is called only after both of the following conditions are met: an account is successfully linked using Link and a transfer is confirmed via the UI. Note that the onSuccess callback only indicates that an account was successfully linked. It does not indicate a successful transfer (i.e., funds movement).

1{
2 institution: {
3 name: 'Wells Fargo',
4 institution_id: 'ins_4'
5 },
6 accounts: [
7 {
8 id: 'ygPnJweommTWNr9doD6ZfGR6GGVQy7fyREmWy',
9 name: 'Plaid Checking',
10 mask: '0000',
11 type: 'depository',
12 subtype: 'checking',
13 verification_status: ''
14 },
15 {
16 id: '9ebEyJAl33FRrZNLBG8ECxD9xxpwWnuRNZ1V4',
17 name: 'Plaid Saving',
18 mask: '1111',
19 type: 'depository',
20 subtype: 'savings'
21 }
22 ...
23 ],
24 transfer_status: 'incomplete',
25 link_session_id: '79e772be-547d-4c9c-8b76-4ac4ed4c441a'
26}

Retrieving additional information about transfer intents

To retrieve more information about a transfer intent, call the /transfer/intent/get endpoint and pass in a transfer intent id (returned by the /transfer/intent/create endpoint).

1const request: TransferIntentGetRequest = {
2 transfer_intent_id: '460cbe92-2dcc-8eae-5ad6-b37d0ec90fd9',
3};
4
5try {
6 const response = await client.transferIntentGet(request);
7} catch (error) {
8 // handle error
9}
1{
2 "transfer_intent": {
3 "account_id": "3gE5gnRzNyfXpBK5wEEKcymJ5albGVUqg77gr",
4 "ach_class": "ppd",
5 "amount": "15.75",
6 "authorization_decision": "APPROVED",
7 "authorization_decision_rationale": null,
8 "created": "2020-08-06T17:27:15Z",
9 "description": "Desc",
10 "failure_reason": null,
11 "id": "460cbe92-2dcc-8eae-5ad6-b37d0ec90fd9",
12 "metadata": {
13 "key1": "value1",
14 "key2": "value2"
15 },
16 "mode": "DISBURSEMENT",
17 "origination_account_id": "9853defc-e703-463d-86b1-dc0607a45359",
18 "status": "SUCCEEDED",
19 "transfer_id": "8945fedc-e703-463d-86b1-dc0607b55460",
20 "user": {
21 "address": {
22 "street": "100 Market Street",
23 "city": "San Francisco",
24 "region": "California",
25 "postal_code": "94103",
26 "country": "US"
27 },
28 "email_address": "lknope@email.com",
29 "legal_name": "Leslie Knope",
30 "phone_number": "123-456-7890"
31 }
32 },
33 "request_id": "saKrIBuEB9qJZno"
34}

The response is a transfer_intent object with more information about the transfer intent.

The status field in the response indicates whether the transfer intent was successfully captured. It can have one of the following values: FAILED, SUCCEEDED, or PENDING.

If the value of status is FAILED, the transfer intent was not captured. The transfer_id field will be "null" and the failure_reason object will contain information about why the transfer intent failed. Some possible reasons may include: the authorization was declined, the account is blocked, or the origination account was invalid.

If the value of status is SUCCEEDED, the transfer intent was successfully captured. The accompanying transfer_id field in the response will be set to the ID of the originated transfer. This ID can be used with other Transfer API endpoints.

A value of PENDING can mean one of the following:

  • The transfer intent has not yet been processed by the authorization engine (authorization_decision is "null"). This is the initial state of all transfer intents.

  • The transfer intent was processed and approved by the authorization engine (authorization_decision is "approved"), but has not yet been processed by the transfer creation processor.

  • The transfer intent was processed by the authorization engine and was declined (authorization_decision is "declined") due to insufficient funds (authorization_decision_rationale.code is "NSF"). If this is the case, the end user can retry the transfer intent up to three times. The transfer intent status will remain as "PENDING" throughout the retry intents. After three unsuccessful retries, the transfer intent status will be "FAILED".

Sample app code

For a real-life example of an app that incorporates Transfer UI, see the Node-based Plaid Pattern Transfer sample app. Pattern Transfer is a sample subscriptions payment app that enables ACH bank transfers. The Transfer UI code can be found in transfers.js

Using Transfer UI with Guarantee

Transfer UI also works with Guarantee. If you are using Guarantee, the default behavior for Transfer UI is to proceed with the transfer regardless of the guarantee decision. If you wish to only allow transfers that are guaranteed by Plaid, you can set require_guarantee to true when calling /transfer/intent/create.

If a transfer is not guaranteed and require_guarantee is set to true, the transfer intent will fail with failure_reason.error_code set to TRANSFER_INTENT_NOT_GUARANTEED.

1{
2 "transfer_intent": {
3 "account_id": "3gE5gnRzNyfXpBK5wEEKcymJ5albGVUqg77gr",
4 "ach_class": "ppd",
5 "amount": "15.75",
6 "authorization_decision": "APPROVED",
7 "authorization_decision_rationale": null,
8 "created": "2019-12-09T17:27:15Z",
9 "description": "Desc",
10 "failure_reason": {
11 "error_code": "TRANSFER_INTENT_NOT_GUARANTEED",
12 "error_message": "transfer intent was not guaranteed",
13 "error_type": "TRANSFER_ERROR"
14 },
15 "guarantee_decision": "NOT_GUARANTEED",
16 "guarantee_decision_rationale": {
17 "code": "RISK_ESTIMATE_UNAVAILABLE",
18 "description": "A risk estimate is unavailable for this item."
19 },
20 "id": "460cbe92-2dcc-8eae-5ad6-b37d0ec90fd9",
21 "metadata": {
22 "key1": "value1",
23 "key2": "value2"
24 },
25 "mode": "PAYMENT",
26 "origination_account_id": "9853defc-e703-463d-86b1-dc0607a45359",
27 "require_guarantee": true,
28 "status": "FAILED",
29 "transfer_id": null,
30 "user": {
31 "address": {
32 "street": "123 Main St.",
33 "city": "San Francisco",
34 "region": "California",
35 "postal_code": "94053",
36 "country": "US"
37 },
38 "email_address": "acharleston@email.com",
39 "legal_name": "Anne Charleston",
40 "phone_number": "510-555-0128"
41 }
42 },
43 "request_id": "saKrIBuEB9qJZno"
44}

Troubleshooting transfers

The onExit callback is called

If the onExit callback is called, the Link session failed to link an account. The transfer intent could not be initiated.

onSuccess is called, but the transfer intent is incomplete

The onSuccess callback is not an indication of a successful transfer creation or a successful transfer (i.e., funds movement). It only indicates the successful linking of an account. To fully diagnose incomplete transfer intents (i.e., when the transfer_status field in the metadata object returned by onSuccess is incomplete), call the /transfer/intent/get endpoint and pass in the corresponding transfer intent ID as an argument. Information about the transfer intent can be found in the status, failure_reason, authorization_decision, and authorization_decision_rationale fields in the response.

Network errors or institution connection issues

Network errors or institution connection issues can also result in incomplete transfer intents. In such cases, generate a new Link token using the existing access_token and invoke Transfer UI to allow the end user to attempt the transfer intent again.

Was this helpful?
Developer community
Github logo
StackOverflow logo
Twitter logo