Moving money with Treasury using InboundTransfer objects
Learn how to transfer money from another account you own into a Treasury financial account.
Inbound transfers move money from an external US bank account into a financial account using the ACH network. These transfers are initiated with InboundTransfer objects.
Inbound transfers take 2-4 business days to complete unless you’re using the same-day ACH capability. For more information, see the Money movement timelines guide.
Note
You can use inbound transfers to move funds from a financial account owner’s bank account. To accept funds from an external party into a financial account, use an ACH Debit into the Payments balance, followed by a payout to the financial account.
Create an InboundTransfer
Use POST /v1/treasury/inbound_
to create an InboundTransfer
object, which represents pull-based transfers from an external account that you own into your financial account. In other words, you create an InboundTransfer
to move funds into your financial account by debiting your external US bank account. You must include the following parameters with your request:
amount
: The amount in cents to be transferred into the financial account.currency
: Three-letter ISO currency code (usd
is currently the only supported value).financial_
: The ID of the financial account receiving the transfer.account origin_
: The source of funds for the inbound transfer. You must first set up the account-attached payment method for inbound flows and verify the bank account using a SetupIntent. Alternatively, you can use an existing BankAccount previously set up as a verified ExternalAccount. Whether you use a payment method or a bank account, you need the account owner’s permission to debit the funds from the account.payment_ method
The following JSON shows the data you can include in the body of your request.
{ // The source PaymentMethod or BankAccount. Funds are pulled from this account. "origin_payment_method": "{{PAYMENT_METHOD_ID}}" | "{{BANK_ACCOUNT_ID}}", // The destination FinancialAccount. Funds arrive in this account. "financial_account": "{{FINANCIAL_ACCOUNT_ID}}", // The amount to debit. 10.00 USD in this case. "amount": 1000, "currency": "usd", // An optional, internal description for the InboundTransfer. "description": "Funds for vendor payment payment_234281", // An optional descriptor for the InboundTransfer to send // to the network with the debit request. Max 10 characters "statement_descriptor": "payment_1", // Stripe does not support updating InboundTransfers after creation. // You can only set metadata at creation time. "metadata": null | {{Hash}} }
The following request transfers 200 USD using an account-attached payment method into the financial account with the provided ID. The Stripe-Account
header value identifies the Stripe account that owns both the financial account and the payment method.
If successful, the response provides the InboundTransfer
object. The object includes a hosted_
that provides access to details of the transaction for the account holder on your platform.
{ "id": "{{INBOUND_TRANSFER_ID}}", "object": "inbound_transfer", "amount": 20000, "created": 1648071297, "currency": "usd", "description": "Funds for repair", "financial_account": "{{FINANCIAL_ACCOUNT_ID}}", "hosted_regulatory_receipt_url": "https://payments.stripe.com/regulatory-receipt/{{IBT_URL}}", "linked_flows": null, "livemode": false, "metadata": {}, "origin_payment_method": "{{PAYMENT_METHOD_ID}}", ... "statement_descriptor": "Invoice 12", "status": "processing", ... }
Warning
In rare cases, Stripe might cancel an InboundTransfer request due to various risk factors. In these scenarios, the API request errors with response code 402. The error message provides additional detail about the risk factors that led to the intervention.
Same-day ACH
Beta
Same-day ACH is currently in beta with limited availability, subject to Stripe review and approval. To request access, email treasury-support@stripe.com.
If you don’t have access, API calls that include same-day ACH features or parameters return an error.
Using same-day ACH enables funds to arrive in the originating financial account within the same business day if the InboundTransfer
call successfully completes before the cut-off time. To use same-day ACH, set the origin_
parameter to same_
.
Note
The fast settlement of same-day ACH inbound transfers can expose your platform to greater financial risk than from standard ACH inbound transfers. For example, a connected account can initiate an inbound transfer that gets returned due to insufficient funds in the source account. Same-day settlement leaves more time to potentially withdraw the funds from the financial account before they’re returned. If the connected account withdraws the funds, and then the return causes a negative balance in the financial account, your platform is responsible
Retrieve an InboundTransfer
Use GET /v1/treasury/inbound_
to retrieve the InboundTransfer
object with the associated ID.
The following JSON shows the data you can include in the body of your request. Some of the parameters in the response have additional details that are only returned when you add them as values to the expand[]
parameter. The fields that you can expand have an “Expandable” comment in the following response example. See Expanding Responses to learn more about expanding object responses.
{ "id": "{{INBOUND_TRANSFER_ID}}", "object": "inbound_transfer", "livemode": false, "created": "{{Timestamp}}", "financial_account": "{{FINANCIAL_ACCOUNT_ID}}", // Expandable "amount": 1000, "currency": "usd", // The only current valid PaymentMethod type for InboundTransfers is us_bank_account "origin_payment_method": "{{PAYMENT_METHOD_ID}}",
The following request retrieves the InboundTransfer
with the id
value of {{INBOUND_
. Including transaction
in the expand[]
array of the body returns the relevant expanded information.
If successful, the response returns the InboundTransfer
object with the expanded information.
{ "id": "{{INBOUND_TRANSFER_ID}}", "object": "inbound_transfer", "amount": 20000, "created": 1648071297, "currency": "usd", "description": "Inbound transfer", "failure_details": null, "financial_account": "{{FINANCIAL_ACCOUNT_ID}}", "hosted_regulatory_receipt_url": "https://payments.stripe.com/regulatory-receipt/{{INBOUND_TRANSFER_ID}}",
List InboundTransfers
Use GET /v1/treasury/inbound_
to retrieve all the InboundTransfers
for the financial account with the associated ID. You can filter the list with the standard list parameters or by status
.
{ // Standard list parameters "limit", "starting_after", "ending_before", // Filter by status "status": "processing" | "succeeded" | "failed", // Filter by FinancialAccount (Required) "financial_account": "{{FINANCIAL_ACCOUNT_ID}}", // Required }
The following request retrieves all the inbound transfers with a status of succeeded
for the financial account with ID {{FINANCIAL_
, which is attached to the connected account with ID {{CONNECTED_
.
InboundTransfer states
The following table describes each status and what the possible transition states are.
STATUS | DESCRIPTION | TRANSITIONS TO STATE |
---|---|---|
processing | The InboundTransfer creation succeeded. Stripe instructs movement of funds on the network. | failed , canceled , succeeded |
failed (terminal) | The InboundTransfer failed to confirm. No transaction was created, and the payment_ hasn’t been debited. | N/A |
canceled (terminal) | The InboundTransfer was cancelled prior to submission to the network. Stripe voids the transaction and no funds are moved from the external bank account. | N/A |
succeeded (terminal) | The InboundTransfer succeeded and funds have landed in the account. A Transaction has been created. InboundTransfers can be returned after succeeding if the external account pulls back their funds, which is represented by a linked ReceivedDebit. | N/A |
Testing InboundTransfers
To test your integration end-to-end, use the SetupIntents requests in test mode to create a PaymentMethod
, then pass that PaymentMethod
into an InboundTransfer
creation request. Valid PaymentMethods
result in succeeded InboundTransfers
, while invalid PaymentMethods
(for example, of unsupported types, containing an unverified bank account, or not set up for inbound flows) throw the same errors as in live mode.
Testing InboundTransfer states
Stripe also provides a set of test PaymentMethod
tokens you can use to trigger specific state transitions:
PAYMENT_METHOD VALUE | RESULT |
---|---|
pm_ | InboundTransfer that transitions from processing to succeeded . |
pm_ | InboundTransfer that remains in the processing state. |
pm_ | InboundTransfer that transitions from processing to failed . |
To test various edge cases more quickly, PaymentMethod
tokens simulate specific failure types:
PAYMENT_METHOD VALUE | RESULT |
---|---|
pm_ | InboundTransfer that transitions to failed with failure_ . |
pm_ | InboundTransfer that transitions to failed with failure_ . |
pm_ | InboundTransfer that transitions to failed with failure_ . |
pm_ | InboundTransfer that transitions to failed with failure_ . |
pm_ | InboundTransfer that transitions to failed with failure_ . |
pm_ | InboundTransfer that transitions from processing to succeeded and is later disputed. inbound_ becomes true , and a linked ReceivedDebit is created. |
In all cases, the InboundTransfer
response begins in the processing
state. You receive webhooks for each relevant state transition, and fetching the InboundTransfer
after creation returns the expected state.
InboundTransfer test helper endpoints
Stripe also provides endpoints that enable you to test InboundTransfers
in different states. Create an InboundTransfer
, then:
Use the test
succeed
endpoint to move the transfer with the associated ID directly into thesucceeded
state.POST /v1/test_
helpers/treasury/inbound_ transfers/{{INBOUND_ TRANSFER_ ID}}/succeed Use the test fail endpoint to move the transfer with the associated ID directly into the
failed
state.POST /v1/test_
helpers/treasury/inbound_ transfers/{{INBOUND_ TRANSFER_ ID}}/fail
These endpoints are particularly useful when testing error scenarios, such as returns, which would otherwise require action from the external account the InboundTransfer was pulling funds from.
Include the optional failure_
parameter in the body to indicate why the transfer failed. If you don’t provide it, the transfer fails with the default could_
failure code.
{ "failure_details": { "code": "account_closed" | "account_frozen" | "bank_account_restricted" | "bank_ownership_changed" | "could_not_process" | // Generic fallback code "invalid_account_number" | "incorrect_account_holder_name" | "invalid_currency" | "no_account" } }
Treasury also provides a return
endpoint to simulate an InboundTransfer that succeeds, but is later returned.
Use the test return endpoint to initiate the simulated return on the InboundTransfer
with the associated ID.
POST /v1/test_
All test endpoints trigger webhooks for each relevant state transition, and fetching the InboundTransfer
after transition returns the expected state.
InboundTransfer webhooks
Stripe emits the following InboundTransfer
events to your webhook endpoint:
treasury.
oninbound_ transfer. created InboundTransfer
creation.treasury.
when aninbound_ transfer. {{new_ status}} InboundTransfer
changes status. Available status value options include:treasury.
inbound_ transfer. succeeded treasury.
inbound_ transfer. failed