Controlling Bank and Debit Card Transfers

Stripe allows platforms to entirely control the transfers for its Managed Accounts. If you need help after reading this, check out our answers to common questions or chat live with other developers in #stripe on freenode.

By default, any charge you make on behalf of a connected account accumulates in the connected account’s Stripe balance and is paid out on a daily rolling basis. However, Stripe offers fine-grained control over this behavior for Managed Accounts.

You can:

Managing bank accounts and debit cards

Managed Accounts have an external_accounts property: a list of all bank accounts and debit cards associated with the Stripe account. Any external account is a possible destination for funds.

{
  ...
  "external_accounts": {
    "object": "list",
    "total_count": 3,
    "has_more": false,
    "url": "/v1/accounts/acct_14qyt6Alijdnw0EA/external_accounts",
    "data": [
      {
        "id": "ba_18u1IUJ6m0aiknMBHFQaTWOj",
        "object": "bank_account",
        "account": "acct_18rsqhJ6m0aiknMB",
        "account_holder_name": "Jane Austen",
        "account_holder_type": "individual",
        "bank_name": "STRIPE TEST BANK",
        "country": "US",
        "currency": "usd",
        "default_for_currency": false,
        "fingerprint": "sSZ2yLp0EZTH17cF",
        "last4": "6789",
        "metadata": {
        },
        "routing_number": "110000000",
        "status": "new"
      },
      {...},
      {...},
    ],
  ...
}

Destination accounts are added via the external_accounts parameter when creating or updating Stripe accounts. The value should be a bank account or debit card token returned from Stripe.js. Alternatively, you can provide a hash of the bank account details, but using Stripe.js is preferred as it prevents sensitive data from hitting your server.

When using debit cards as a transfer destination, the following restrictions apply:

  • Must be a non-prepaid US Visa or Mastercard
  • Limited to $9,999 (USD) per transfer on Instant Payouts
  • Generally limited to $3,000 (USD) per transfer otherwise

Managing multiple bank and debit accounts

By default, providing a new value for external_accounts while updating a managed account replaces the existing account with the new one. To add additional bank accounts or debit cards to a connected account, use the Bank Account and Card creation API endpoints.

curl https://api.stripe.com/v1/accounts/CONNECTED_STRIPE_ACCOUNT_ID/external_accounts \
   -u {PLATFORM_SECRET_KEY}: \
   -d external_account=btok_9CUINZPUJnubtQ
Stripe.api_key = PLATFORM_SECRET_KEY
account = Stripe::Account.retrieve(CONNECTED_STRIPE_ACCOUNT_ID)
account.external_accounts.create(:external_account => "btok_9CUUxVXui4TQyJ")
stripe.api_key = PLATFORM_SECRET_KEY
account = stripe.Account.retrieve(CONNECTED_STRIPE_ACCOUNT_ID)
account.external_accounts.create(external_account="btok_9CUY3ZiKZEHSlb")
\Stripe\Stripe::setApiKey(PLATFORM_SECRET_KEY);
$account = \Stripe\Account::retrieve(CONNECTED_STRIPE_ACCOUNT_ID);
$account->external_accounts->create(array(
    "external_account" => "btok_9CUINZPUJnubtQ",
));
Stripe.apiKey = PLATFORM_SECRET_KEY;
Account account = Account.retrieve(CONNECTED_STRIPE_ACCOUNT_ID, null);
Map<String, Object> params = new HashMap<String, Object>();
params.put("external_account", "btok_9CUaZTAdbT4G5z");
account.getExternalAccounts().create(params);
var stripe = require('stripe')(PLATFORM_SECRET_KEY);
stripe.accounts.createExternalAccount(
  CONNECTED_STRIPE_ACCOUNT_ID,
  {external_account: "btok_9CUYdoUSROb2yg"}
);

When working with multiple currencies, Stripe automatically sends transfers to an associated bank account or debit card for its currency, thereby avoiding exchange fees. When there are multiple accounts available for a given currency, Stripe uses the one set as default_for_currency.

Stripe maintains a list of available country/currency combinations for your reference and to help your users choose from the supported options.

Payout information

When using automatic transfers, the transfer_schedule property on an account indicates how often the Stripe account’s balance is automatically paid out:

{
  ...
  "transfer_schedule": {
    "delay_days": 7,
    "interval": "daily"
  },
  ...
}

The delay_days property reflects how long it takes charges (or linked transfers) to become available for payout. This field is useful for controlling automatic payouts. For example, if you want your Managed Accounts to receive their funds 2 weeks after the charge is made, set interval to daily and delay_days to 14. The default is the lowest permitted value for the account, determined by the connected account’s country. When setting or updating this field, you may pass the string minimum to choose the lowest permitted value.

There are four possible settings for the interval property:

  • manual prevents automatic payouts. You will have to manually pay out the account’s balance using the Transfers API (acting as the connected account). Also set an account to manual to use Instant Payouts.
  • daily automatically pays out charges delay_days days after they’re created. The delay_days value cannot be less than your own transfer schedule or less than the default transfer schedule for the account.
  • weekly automatically pays out the balance once a week, specified by the weekly_anchor parameter (a lower-case weekday such as monday).
  • monthly automatically pays out the balance once a month, specified by the monthly_anchor parameter (a number from 1 to 31).

Using manual transfers

If you set transfer_schedule[interval] to manual using the Accounts API, Stripe will hold funds in the account holder’s balance until told to pay them out (or until a maximum of 30 days have passed). To trigger a payout of these funds, use the Transfers API.

The Transfers API is only for moving funds from a connected Stripe account’s balance into their external account. To move funds between Stripe accounts, see special-case transfers or charging through the platform.

Standard transfers

As a basic transfer example, to have $10 sent from a managed account’s Stripe balance to their external account:

curl https://api.stripe.com/v1/transfers \
   -u {PLATFORM_SECRET_KEY}: \
   -H "Stripe-Account: {CONNECTED_STRIPE_ACCOUNT_ID}" \
   -d amount=1000 \
   -d currency=usd \
   -d destination=default_for_currency
Stripe.api_key = PLATFORM_SECRET_KEY
Stripe::Transfer.create(
  {
    :amount => 1000,
    :currency => "usd",
    :destination => "default_for_currency"
  },
  {:stripe_account => CONNECTED_STRIPE_ACCOUNT_ID}
)
stripe.api_key = PLATFORM_SECRET_KEY
stripe.Transfer.create(
  amount=1000,
  currency="usd",
  destination="default_for_currency",
  stripe_account=CONNECTED_STRIPE_ACCOUNT_ID
)
\Stripe\Stripe::setApiKey(PLATFORM_SECRET_KEY);
\Stripe\Transfer::create(
  array(
    "amount" => 1000,
    "currency" => "usd",
    "destination" => "default_for_currency"
  ),
  array("stripe_account" => CONNECTED_STRIPE_ACCOUNT_ID)
);
Stripe.apiKey = PLATFORM_SECRET_KEY;
RequestOptions requestOptions = RequestOptions.builder().setStripeAccount(CONNECTED_STRIPE_ACCOUNT_ID).build();

Map<String, Object> transferParams = new HashMap<String, Object>();
transferParams.put("amount", 1000);
transferParams.put("currency", "usd");
transferParams.put("destination", "default_for_currency");

Transfer.create(transferParams, requestOptions);
var stripe = require('stripe')(PLATFORM_SECRET_KEY);
stripe.transfers.create(
  {
    amount: 1000,
    currency: "usd",
    destination: "default_for_currency"
  },
  {stripe_account: CONNECTED_STRIPE_ACCOUNT_ID}
);

Setting destination=default_for_currency tells Stripe to transfer to the account’s default bank account or debit card for the given currency.

With a standard transfer, you can payout up to the user’s available balance. To find that amount, perform a retrieve balance call on their behalf.

Stripe tracks balance contributions from different payment sources in separate balances. The retrieve balance response breaks down the components of each balance by source type. For example, if you want to create a transfer specifically for a non credit-card balance, specify the source_type in your request.

curl https://api.stripe.com/v1/transfers \
   -u {PLATFORM_SECRET_KEY}: \
   -d amount=24784 \
   -d currency=usd \
   -d destination=default_for_currency \
   -d source_type=bank_account
Stripe.api_key = PLATFORM_SECRET_KEY
Stripe::Transfer.create(
  :amount => 24784,
  :currency => 'usd',
  :destination => 'default_for_currency',
  :source_type => 'bank_account',
)
stripe.api_key = PLATFORM_SECRET_KEY
stripe.Transfer.create(
  amount=24784,
  currency="usd",
  destination="default_for_currency",
  source_type="bank_account",
)
\Stripe\Stripe::setApiKey(PLATFORM_SECRET_KEY);
$tr = \Stripe\Transfer::create(array(
  "amount" => 24784,
  "currency" => "usd",
  "destination" => "default_for_currency",
  "source_type" => "bank_account"
));
Stripe.apiKey = PLATFORM_SECRET_KEY;

Map<String, Object> transferParams = new HashMap<String, Object>();
transferParams.put("amount", 24784);
transferParams.put("currency", "usd");
transferParams.put("destination", "default_for_currency");
transferParams.put("source_type", "bank_account");

Transfer.create(transferParams);

var stripe = require('stripe')(PLATFORM_SECRET_KEY);
stripe.transfers.create(
  {
    amount: 24784,
    currency: 'usd',
    destination: 'default_for_currency',
    source_type: 'bank_account',
  }
);

Note that it is possible for any source’s balance component to go negative (through refunds or chargebacks), and transfers can’t be created for greater than the aggregate available balance.

Using Instant Payouts (US only)

With Instant Payouts, you can immediately send funds to a managed account’s debit card. Funds typically appear in the associated bank account within 30 minutes, making it possible to go from charge to payout in mere moments.

To use Instant Payouts, specify instant for the method property when creating the transfer:

curl https://api.stripe.com/v1/transfers \
   -u {PLATFORM_SECRET_KEY}: \
   -H "Stripe-Account: {CONNECTED_STRIPE_ACCOUNT_ID}" \
   -d amount=1000 \
   -d currency=usd \
   -d method=instant
Stripe.api_key = PLATFORM_SECRET_KEY
Stripe::Transfer.create(
  {
    :amount => 1000,
    :currency => "usd",
    :method => "instant"
  },
  {:stripe_account => CONNECTED_STRIPE_ACCOUNT_ID}
)
stripe.api_key = PLATFORM_SECRET_KEY
stripe.Transfer.create(
  amount=1000,
  currency="usd",
  method="instant",
  stripe_account=CONNECTED_STRIPE_ACCOUNT_ID
)
\Stripe\Stripe::setApiKey(PLATFORM_SECRET_KEY);
\Stripe\Transfer::create(
  array(
    "amount" => 1000,
    "currency" => "usd",
    "method" => "instant"
  ),
  array("stripe_account" => CONNECTED_STRIPE_ACCOUNT_ID)
);
Stripe.apiKey = PLATFORM_SECRET_KEY;
RequestOptions requestOptions = RequestOptions.builder().setStripeAccount(CONNECTED_STRIPE_ACCOUNT_ID).build();

Map<String, Object> transferParams = new HashMap<String, Object>();
transferParams.put("amount", 1000);
transferParams.put("currency", "usd");
transferParams.put("method", "instant");

Transfer.create(transferParams, requestOptions);
var stripe = require('stripe')(PLATFORM_SECRET_KEY);
stripe.transfers.create(
  {
    amount: 1000,
    currency: "usd",
    method: "instant"
  },
  {stripe_account: CONNECTED_STRIPE_ACCOUNT_ID}
);

Instant Payouts differ from other manual transfers in a couple of ways:

  • You can transfer an account’s available balance plus its pending balance
  • Instant Payouts can be requested on weekends and holidays

Initially, platforms can transfer up to $5,000 (USD) per day–in total, across all connected accounts–through Instant Payouts. Contact us if you need this threshold increased.

Instant Payouts is available for all of the largest US banks, but a small percentage of banks do not yet support it. For those banks, transfers will arrive in the next 1-2 business days.

You should also make sure any marketing for Instant Payouts clearly discloses that funds may take up to two business days to appear in some bank accounts.

Using webhooks with transfers

All transfer activity on connected accounts can be tracked using webhooks. (When using Connect, you should always be using webhooks.) Specific to transfers, you’ll see these events:

  • transfer.created
  • transfer.updated
  • transfer.paid
  • transfer.failed

For most transfers, these event notifications occur over a series of days. For Instant Payouts, transfer.paid will be sent on the next business day, although the funds should be received well beforehand.

If a transfer cannot be completed, a transfer.failed event occurs. The event’s failure_reason property indicates why.

Handling negative balances

Sometimes Managed Accounts develop negative balances, usually due to refunds. When that happens, Stripe tracks the negative balance, and reconciles it against new funds going into the account. While an account’s balance is negative, you cannot send transfers to the account. Stripe will resume sending transfers to the managed account once the account’s Stripe balance is again positive.

In the end you–the platform–are responsible for any negative balances on your Managed Accounts (this is not the case for Standalone Accounts). To ensure funds are covered, Stripe will hold a reserve on your platform account’s balance to cover the negative balances across your Managed Accounts.

That being said, Stripe does provide assistance in recovering funds from negative balances. You may set the debit_negative_balances flag on Managed Accounts, indicating whether or not Stripe should attempt to debit the managed account’s bank account to cover amounts owed. This is not always possible, depending on the country and bank account. Stripe cannot automatically withdraw from debit cards.

Further reading

Check out the rest of the guide for help with your integration.