=>
(
await dwollaClient.post(`customers/${options.customerId}/funding-sources`, {
_links: {
exchange: {
href: options.exchangeUrl,
},
},
bankAccountType: options.type,
name: options.fundingSourceName,
})
).headers.get("location");
```
## Frequently Asked Questions
When creating a funding source, a Plaid processor\_token value is passed in via an exchange resource. With this information, Dwolla executes a call to Plaid's API to securely retrieve the account and routing number and creates a funding source on your behalf. Upon success, Dwolla returns a URL that represents the new funding source via the location response header.
Yes, if the end user has 2FA enabled with their bank, Plaid will attempt to mimic their bank's login flow, meaning that the user should receive a 2FA code, and is subsequently prompted to enter it in on the next screen.
If the processor\_token has already been used to create a funding source, then the token changing or expiring will not affect it; Dwolla only uses the processor\_token to fetch the account and routing number from Plaid and then immediately discards it.
However, if you have not yet used the processor\_token to create a funding source, then you will need to follow Plaid's process for creating a new token before sending it over to Dwolla.
Yes, it is possible to use Plaid Link to both automatically verify bank accounts and manually verify via micro-deposits if the bank is not supported or an error occurs during automatic verification.
No. Since Dwolla only uses Plaid to securely retrieve the account and routing number, any changes to the underlying Plaid account will not affect the funding source(s) that have already been created in Dwolla.
This issue can occur when the user's bank utilizes Tokenized Account Numbers (TANs) for bank verification, and Plaid provides Dwolla with the TAN instead of the actual Account Number. Dwolla processes the ACH payment using the TAN. If the user has disabled TAN transactions on their bank's website, the ACH payment made by Dwolla will fail with an ACH return (e.g., R04 - Invalid Account Number).
To resolve this issue, please advise the user to contact their bank or visit their bank's website and enable TANs for future transfers. In some cases, it might be necessary to re-add the Plaid verified bank using a new processor token.
# Send Money
Source: https://developers.dwolla.com/docs/send-money
Learn the key steps involved with sending funds to your end user's bank account.
## Overview
This guide is designed to get you up and running quickly with digital disbursements by creating a one-time transfer to an end user via the Dwolla API. In this guide we'll cover the basics of integrating the most lightweight payment flow, sending funds (also referred to as "payouts"), by outlining and walking through the necessary steps, to create a bank transfer. For simplicity, we'll represent a one-to-one transfer between two users, where the source user is identified as the Master Dwolla account and the destination user is an individual or business that has been on-boarded via the Dwolla API.
If your use case involves sending several digital disbursements in a single batch, it's recommended that you leverage our [Mass Payment API](https://developers.dwolla.com/api-reference/mass-payments), which allows you to send up to 5,000 payments with a single API request.
### Key Concepts
In this quickstart guide, you'll learn the key concepts involved with sending money to a recipient's bank account via digital disbursements:
Select the appropriate Customer type (receive-only, verified, etc.) and create a new Customer record through the Dwolla API to represent your recipient.
Add a bank account as a funding source to your recipient's Customer record, which will serve as the destination for the funds transfer.
Get a list of verified funding sources from both your Dwolla account and the recipient's account to use as source and destination for the transfer.
Initiate a transfer from your verified funding source to the recipient's bank account using the Dwolla API.
## Before You Begin
We encourage you to create a sandbox account, if you haven't already. This will allow you to follow along with the steps outlined in this guide. Check out our [Sandbox guide](/docs/testing) to learn more.
After creating a sandbox account, you'll obtain your API Key and Secret, which are used to obtain an OAuth access token. An access token is required in order to authenticate against the Dwolla API. If you haven't already, run through the [Quickstart](/docs/quickstart) to get your first token, or see the [Authentication guide](/docs/api-reference/api-fundamentals/making-requests-and-authentication) for OAuth details.
Lastly, in this sandbox walkthrough, we recommend having an active webhook subscription. This will help notify your application of various events that occur within Dwolla. [Check out our guide to learn more](/docs/working-with-webhooks).
**Let's get started!**
## Step 1 - Create a recipient
Before your end user can receive funds to their connected bank account, they must be created as a Customer via the Dwolla API. The ability to send funds to end users is very flexible in that all Customer types can be used to leverage this funds flow. To learn more about the different types of Customers and the capabilities of each, check out our [developer resource article](/docs/customer-types).
### Create the Customer
While you can use any Customer type for this funds flow, we will be creating a `receive-only` user in this guide, as it offers a lightweight onboarding experience for users. Just as the name implies, receive-only users are only eligible to receive funds into their attached bank account.
Providing the IP address of the end user accessing your application as the
ipAddress parameter. This enhances fraud detection and tracking.
##### Request Parameters - Receive-only User
| Parameter | Required? | Type | Description |
| ------------ | --------- | ------ | ----------------------------------------------------------------------- |
| firstName | yes | string | Customer's first name |
| lastName | yes | string | Customer's last name |
| email | yes | string | Customer's email address |
| type | yes | string | Value of `receive-only` |
| businessName | no | string | Customer's registered business name (optional if not a business entity) |
| ipAddress | no | string | Customer's IP address |
```bash create customer theme={"dark"}
POST https://api-sandbox.dwolla.com/customers
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNicvoWhDbQcVSKLRUpGjIdlPSEYyrHqrDDoRnQwE7Q
{
"firstName": "Jane",
"lastName": "Merchant",
"email": "jmerchant@nomail.net",
"type": "receive-only",
"ipAddress": "99.99.99.99"
}
HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747
```
```ruby create_customer.rb theme={"dark"}
request_body = {
:firstName => 'Jane',
:lastName => 'Merchant',
:email => 'jmerchant@nomail.net',
:type => 'receive-only',
:ipAddress => '99.99.99.99'
}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
customer = app_token.post "customers", request_body
customer.response_headers[:location] # => "https://api-sandbox.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747"
```
```javascript createCustomer.js theme={"dark"}
var requestBody = {
firstName: "Jane",
lastName: "Merchant",
email: "jmerchant@nomail.net",
type: "receive-only",
ipAddress: "99.99.99.99",
};
dwolla.post("customers", requestBody).then(function (res) {
res.headers.get("location"); // => 'https://api-sandbox.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747'
});
```
```python create_customer.py theme={"dark"}
request_body = {
'firstName': 'Jane',
'lastName': 'Merchant',
'email': 'jmerchant@nomail.net',
'type': 'receive-only',
'ipAddress': '99.99.99.99'
}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
customer = app_token.post('customers', request_body)
customer.headers['location'] # => 'https://api-sandbox.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747'
```
```php createCustomer.php theme={"dark"}
create([
'firstName' => 'Jane',
'lastName' => 'Merchant',
'email' => 'jmerchant@nomail.net',
'type' => 'receive-only'
'ipAddress' => '99.99.99.99'
]);
print($customer); # => "https://api-sandbox.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747"
?>
```
When the Customer is successfully created on your application, you will receive a `201` HTTP response with an empty response body. You can reference the Location header to retrieve a link that represents the created Customer resource. We recommend storing the full URL for future use, as it will be needed for actions such as attaching a bank or correlating webhooks that are triggered for the user in the Dwolla system.
### Handle Webhooks
If you have an active [webhook subscription](/docs/working-with-webhooks), you will receive the `customer_created` webhook immediately after the resource has been created.
## Step 2 - Adding a Funding Source
After creating our receive-only User, the next step is to attach a bank funding source. This will be the funding source where they will receive funds.
#### Bank Addition and Verification methods
Within Dwolla, the sending party must always verify their bank account in order to be eligible to create a transfer. Although it's recommended, the party that is receiving the funds does not need to undergo bank verification.
There are three ways of adding a bank to a Customer with the Dwolla API. A simplified table below outlines the similarities and differences of each method.
| Bank Addition Method | Will the bank be `verified`? | Required Information |
| --------------------------------------------------------------------------------------------------------------------- | ----------------------------- | ------------------------------- |
| API - Account & Routing Number | Optional - With Microdeposits | Bank Account and Routing Number |
| [Dwolla + Open Banking](/docs/open-banking) | Yes | Online banking credentials |
| [Drop-in components](/docs/drop-in-components) | Optional - With Microdeposits | Bank Account and Routing Number |
| Third Party - Plaid ([Example](https://github.com/Dwolla/integration-examples/tree/main/dwolla-plaid-funding-source)) | Yes | Online Bank Credentials |
For more information on securely submitting a user's bank details directly to
Dwolla from the client-side of your application, reference our Drop-in Components
.
### Adding a Bank to the Receive-only User
In this step, we will be adding a bank account to our receive-only user by collecting their bank details within a form on our application. After initial validation of the form fields, the user's bank details will be submitted to our back-end server where the API request is made to Dwolla to add a bank account.
##### Request Parameters - Create a Funding Source
| Parameter | Required? | Type | Description |
| --------------- | --------- | ------ | ------------------------------------------------------------------------ |
| routingNumber | yes | string | The bank routing number |
| accountNumber | yes | string | The bank account number |
| bankAccountType | yes | string | Type of bank account: `checking` or `savings` |
| name | yes | string | Arbitrary nickname for the funding source. Must be 50 characters or less |
```bash create customer funding source theme={"dark"}
POST https://api.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747/funding-sources
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNicvoWhDbQcVSKLRUpGjIdlPSEYyrHqrDDoRnQwE7Q
{
"routingNumber": "222222226",
"accountNumber": "123456789",
"bankAccountType": "checking",
"name": "Jane Merchant - Checking 6789"
}
HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31
```
```ruby create_funding_source.rb theme={"dark"}
customer_url = 'https://api-sandbox.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747'
request_body = {
routingNumber: '222222226',
accountNumber: '123456789',
bankAccountType: 'checking',
name: 'Jane Merchant - Checking 6789'
}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
funding_source = app_token.post "#{customer_url}/funding-sources", request_body
funding_source.response_headers[:location] # => "https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31"
```
```javascript create_funding_source.js theme={"dark"}
var customerUrl =
"https://api-sandbox.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747";
var requestBody = {
routingNumber: "222222226",
accountNumber: "123456789",
bankAccountType: "checking",
name: "Jane Merchant - Checking 6789",
};
dwolla.post(`${customerUrl}/funding-sources`, requestBody).then(function (res) {
res.headers.get("location"); // => 'https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31'
});
```
```python create_funding_source.py theme={"dark"}
customer_url = 'https://api-sandbox.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747'
request_body = {
'routingNumber': '222222226',
'accountNumber': '123456789',
'bankAccountType': 'checking',
'name': 'Jane Merchant - Checking 6789'
}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
customer = app_token.post('%s/funding-sources' % customer_url, request_body)
customer.headers['location'] # => 'https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31'
```
```php create_funding_source.php theme={"dark"}
createCustomerFundingSource([
"routingNumber" => "222222226",
"accountNumber" => "123456789",
"bankAccountType" => "checking",
"name" => "Jane Merchant - Checking 6789"
], "https://api-sandbox.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747"
);
print($new_fs); # => https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31
?>
```
When the funding source is created, you will receive a `201` HTTP response with an empty response body. You can refer to the Location header to retrieve a link to the created funding source resource. We recommend storing the full URL for future use as it will be referenced when creating the transfer to this user's bank account.
### Handle Webhooks
If you have an active webhook subscription (required in production & optional in Sandbox), you will receive the `customer_funding_source_created` webhook immediately after the resource has been created.
## Step 3 - Retrieve funding sources
Now that you've created a Customer and associated its funding source, you are close to being able to initiate your first transfer. The transfer requires the following information:
* The funding source to pull the funds from (a bank attached to your Dwolla Master Account)
* The funding source to push the funds to (a bank attached to your created Customer)
Dwolla uses URLs to represent relations between resources. Therefore, you'll need to provide the full URL of the funding source when creating the transfer.
### Retrieve your Dwolla Master Account's list of available Funding Sources
Use the [list an account's funding sources endpoint](/docs/api-reference/accounts/list-funding-sources-for-an-account) to fetch a list of your own funding sources. You'll need your account URL which can be retrieved by calling [the Root](/docs/api-reference/root) of the API.
##### Request and response
```bash List Funding Sources [expandable] theme={"dark"}
GET https://api-sandbox.dwolla.com/accounts/ad5f2162-404a-4c4c-994e-6ab6c3a13254/funding-sources?removed=false
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNicvoWhDbQcVSKLRUpGjIdlPSEYyrHqrDDoRnQwE7Q
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/accounts/ad5f2162-404a-4c4c-994e-6ab6c3a13254/funding-sources"
}
},
"_embedded": {
"funding-sources": [
{
"_links": {
"transfer-from-balance": {
"href": "https://api-sandbox.dwolla.com/transfers",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
},
"self": {
"href": "https://api-sandbox.dwolla.com/funding-sources/b5e68264-7d4d-42a9-88d4-5616c77c6baa",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "funding-source"
},
"remove": {
"href": "https://api-sandbox.dwolla.com/funding-sources/b5e68264-7d4d-42a9-88d4-5616c77c6baa",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "funding-source"
},
"transfer-send": {
"href": "https://api-sandbox.dwolla.com/transfers",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
},
"transfer-receive": {
"href": "https://api-sandbox.dwolla.com/transfers",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
},
"account": {
"href": "https://api-sandbox.dwolla.com/accounts/ad5f2162-404a-4c4c-994e-6ab6c3a13254",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "account"
}
},
"id": "b5e68264-7d4d-42a9-88d4-5616c77c6baa",
"status": "verified",
"type": "bank",
"bankAccountType": "checking",
"name": "ABC Bank Checking",
"created": "2019-03-14T15:18:51.336Z",
"removed": false,
"channels": [
"ach"
],
"bankName": "SANDBOX TEST BANK"
},
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/funding-sources/b268f6b9-db3b-4ecc-83a2-8823a53ec8b7",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "funding-source"
},
"balance": {
"href": "https://api-sandbox.dwolla.com/funding-sources/b268f6b9-db3b-4ecc-83a2-8823a53ec8b7/balance",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "balance"
},
"transfer-send": {
"href": "https://api-sandbox.dwolla.com/transfers",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
},
"with-available-balance": {
"href": "https://api-sandbox.dwolla.com/funding-sources/b268f6b9-db3b-4ecc-83a2-8823a53ec8b7",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "funding-source"
},
"transfer-receive": {
"href": "https://api-sandbox.dwolla.com/transfers",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
},
"account": {
"href": "https://api-sandbox.dwolla.com/accounts/ad5f2162-404a-4c4c-994e-6ab6c3a13254",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "account"
}
},
"id": "b268f6b9-db3b-4ecc-83a2-8823a53ec8b7",
"status": "verified",
"type": "balance",
"name": "Balance",
"created": "2014-07-09T20:39:33.000Z",
"removed": false,
"channels": []
}
]
}
}
```
```ruby get_funding_sources.rb theme={"dark"}
account_url = 'https://api.dwolla.com/accounts/ad5f2162-404a-4c4c-994e-6ab6c3a13254'
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
funding_sources = app_token.get "#{account_url}/funding-sources?removed=false"
funding_sources._embedded['funding-sources'][0].name # => "ABC Bank Checking"
```
```javascript get_funding_sources.js theme={"dark"}
var accountUrl =
"https://api-sandbox.dwolla.com/accounts/ad5f2162-404a-4c4c-994e-6ab6c3a13254";
dwolla.get(`${accountUrl}/funding-sources?removed=false`).then(function (res) {
res.body._embedded["funding-sources"][0].name; // => 'ABC Bank Checking'
});
```
```python get_funding_sources.py theme={"dark"}
account_url = 'https://api.dwolla.com/accounts/ad5f2162-404a-4c4c-994e-6ab6c3a13254'
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
funding_sources = app_token.get('%s/funding-sources?removed=false' % account_url)
funding_sources.body['_embedded']['funding-sources'][0]['name'] # => 'ABC Bank Checking'
```
```php get_funding_sources.php theme={"dark"}
getAccountFundingSources($accountUrl, $removed = false);
# Access desired information in response object fields
print($fundingSources->_embedded) # => PHP associative array of _embedded contents in schema
?>
```
When the funding sources list is successfully retrieved, you will receive a `200` HTTP response with the details of each funding source. After retrieving your list of funding sources, we recommend storing the full URL for future use as it will be referenced when creating the transfer to your user's bank account.
### Retrieve your Customer's list of available funding sources
Use the [list an Customer's funding sources](/docs/api-reference/funding-sources/list-customer-funding-sources) endpoint to fetch a list of your own funding sources. You'll need the Customer URL which can be [retrieved from the API.](/docs/api-reference/customers/list-and-search-customers)
##### Request and response
```bash List Customer Funding Sources [expandable] theme={"dark"}
GET https://api-sandbox.dwolla.com/customers/ad5f2162-404a-4c4c-994e-6ab6c3a13254/funding-sources
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNicvoWhDbQcVSKLRUpGjIdlPSEYyrHqrDDoRnQwE7Q
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/customers/ad5f2162-404a-4c4c-994e-6ab6c3a13254/funding-sources"
}
},
"_embedded": {
"funding-sources": [
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/funding-sources/0094b1b4-e171-4dc8-865b-cb121c2377bb"
},
"customer": {
"href": "https://api-sandbox.dwolla.com/customers/ad5f2162-404a-4c4c-994e-6ab6c3a13254"
},
"with-available-balance": {
"href": "https://api-sandbox.dwolla.com/funding-sources/0094b1b4-e171-4dc8-865b-cb121c2377bb"
}
},
"id": "0094b1b4-e171-4dc8-865b-cb121c2377bb",
"status": "verified",
"type": "balance",
"name": "Balance",
"created": "2013-09-07T14:42:52.000Z"
},
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/funding-sources/b5e68264-7d4d-42a9-88d4-5616c77c6baa"
},
"customer": {
"href": "https://api-sandbox.dwolla.com/customers/ad5f2162-404a-4c4c-994e-6ab6c3a13254"
}
},
"id": "b5e68264-7d4d-42a9-88d4-5616c77c6baa",
"status": "verified",
"type": "bank",
"name": "ABC Bank Checking",
"created": "2014-09-04T23:19:19.543Z"
}
]
}
}
```
```ruby list_customer_funding_sources.rb theme={"dark"}
customer_url = 'https://api.dwolla.com/customers/ad5f2162-404a-4c4c-994e-6ab6c3a13254'
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
funding_sources = app_token.get "#{customer_url}/funding-sources"
funding_sources._embedded['funding-sources'][0].name # => "ABC Bank Checking"
```
```javascript list_customer_funding_sources.js theme={"dark"}
var customerUrl =
"https://api-sandbox.dwolla.com/customers/ad5f2162-404a-4c4c-994e-6ab6c3a13254";
dwolla.get(`${accountUrl}/funding-sources`).then(function (res) {
res.body._embedded["funding-sources"][0].name; // => 'ABC Bank Checking'
});
```
```python list_customer_funding_sources.py theme={"dark"}
customers_url = 'https://api.dwolla.com/customers/ad5f2162-404a-4c4c-994e-6ab6c3a13254'
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
funding_sources = app_token.get('%s/funding-sources' % customer_url)
funding_sources.body['_embedded']['funding-sources'][0]['name'] # => 'ABC Bank Checking'
```
```php list_customer_funding_sources.php theme={"dark"}
getCustomerFundingSources($customerUrl);
# Access desired information in response object fields
print($fundingSources->_embedded) # => PHP associative array of _embedded contents in schema
?>
```
When the list of funding sources is successfully retrieved, you will receive a `200` HTTP response with the details for the funding sources. After retrieving the funding sources, we recommend storing the full URL for future use as it will be referenced when creating the transfer to this user's bank account.
## Step 4 - Initiating a transfer
Now that our user is onboarded and their funding source has been created, we're ready to create a transfer to their bank account. In order to create the transfer, we'll need Funding Source links that represent both the source and destination bank accounts. Your customer's funding source URL should be stored from the previous step and retrieved on demand when creating the transfer.
#### Identify Source and Destination Parties
Since you are utilizing a `send` funds flow, you will need to ensure that you know exactly who will be receiving these funds.
* Source - Your Dwolla Master Account Bank Funding Source
* Destination - Your Customer's Bank Funding Source
### Initiate a Transfer
To initiate a transfer, we will need to specify the funding source URLs in the `_links` parameter.
| Parameter | Required? | Type | Description |
| --------- | --------- | ------ | ---------------------------------------------------------------------------------- |
| \_links | yes | object | A \_links JSON object describing the desired source and destination of a transfer. |
| amount | yes | object | An amount JSON object. |
##### Request and response
```bash Create Transfer theme={"dark"}
POST https://api-sandbox.dwolla.com/transfers
Accept: application/vnd.dwolla.v1.hal+json
Content-Type: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNicvoWhDbQcVSKLRUpGjIdlPSEYyrHqrDDoRnQwE7Q
{
"_links": {
"source": {
"href": "https://api-sandbox.dwolla.com/funding-sources/b5e68264-7d4d-42a9-88d4-5616c77c6baa"
},
"destination": {
"href": "https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e"
}
},
"amount": {
"currency": "USD",
"value": "225.00"
}
}
...
HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388
```
```ruby create_transfer.rb theme={"dark"}
transfer_request = {
:_links => {
:source => {
:href => "https://api-sandbox.dwolla.com/funding-sources/b5e68264-7d4d-42a9-88d4-5616c77c6baa"
},
:destination => {
:href => "https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e"
}
},
:amount => {
:currency => "USD",
:value => "225.00"
}
}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
transfer = app_token.post "transfers", transfer_request
transfer.response_headers[:location] # => "https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388"
```
```javascript create_transfer.js theme={"dark"}
var transferRequest = {
_links: {
source: {
href: "https://api-sandbox.dwolla.com/funding-sources/b5e68264-7d4d-42a9-88d4-5616c77c6baa",
},
destination: {
href: "https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e",
},
},
amount: {
currency: "USD",
value: "225.00",
},
};
dwolla.post("transfers", transferRequest).then(function (res) {
res.headers.get("location"); // => 'https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388'
});
```
```python create_transfer.py theme={"dark"}
transfer_request = {
'_links': {
'source': {
'href': 'https://api-sandbox.dwolla.com/funding-sources/b5e68264-7d4d-42a9-88d4-5616c77c6baa'
},
'destination': {
'href': 'https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e'
}
},
'amount': {
'currency': 'USD',
'value': '225.00'
}
}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
transfer = app_token.post('transfers', transfer_request)
transfer.headers['location'] # => 'https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388'
```
```php create_transfer.php theme={"dark"}
array (
'source' =>
array (
'href' => 'https://api-sandbox.dwolla.com/funding-sources/b5e68264-7d4d-42a9-88d4-5616c77c6baa',
),
'destination' =>
array (
'href' => 'https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e',
),
),
'amount' =>
array (
'currency' => 'USD',
'value' => '225.00',
)
);
$transferApi = new DwollaSwagger\TransfersApi($apiClient);
$transfer = $transferApi->create($transfer_request);
print($transfer); # => https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388
?>
```
When the transfer is created, you will receive a `201` HTTP response with an empty response body. You can refer to the Location header to retrieve a link to the created Transfer resource. All transactions that are sourced from a bank or that are going to a bank will have an initial status of `pending`. We recommend storing the full Transfer URL for future use, as it will be needed for correlating transfer update webhooks that are triggered for the user in the Dwolla system.
### Handle Webhooks
A single API call to create a payment transfer can trigger several transfer-related webhook events. The number of webhooks and type of webhook events can vary depending on the Customer type(s) involved in the transfer, as well as the source and destination for the funds transfer.
For more information on which webhooks will be fired, refer to our [API Reference Docs](/docs/api-reference/events).
### Simulate ACH Processing
To simulate ACH processing in the Dwolla Sandbox environment, navigate to the Sandbox Dashboard. From here, you will want to click the "Process Bank Transfers" button on the top of the screen. Your Sandbox transfer will be moved out of a `pending` status and moved to a `processed` status.
### Verify Status of Transfer
Since ACH transactions can take a few days to complete, webhooks are an efficient way to notify you of when a transfer is completed and `processed` to a destination funding source. However, if you want to verify the status of a transfer at any given point in time, you can make a call to the API to retrieve the transfer by its unique id.
```bash Retrieve Transfer Status theme={"dark"}
GET https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNicvoWhDbQcVSKLRUpGjIdlPSEYyrHqrDDoRnQwE7Q
{
"_links": {
"cancel": {
"href": "https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388",
"type": "transfer"
},
"source": {
"href": "https://api-sandbox.dwolla.com/accounts/ad5f2162-404a-4c4c-994e-6ab6c3a13254",
"type": "account"
},
"funding-transfer": {
"href": "https://api-sandbox.dwolla.com/transfers/e73f5b8e-e458-e611-80e5-0aa34a9b2388",
"type": "transfer"
},
"self": {
"href": "https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388",
"type": "transfer"
},
"source-funding-source": {
"href": "https://api-sandbox.dwolla.com/funding-sources/b5e68264-7d4d-42a9-88d4-5616c77c6baa",
"type": "funding-source"
},
"destination": {
"href": "https://api-sandbox.dwolla.com/customers/c7f300c0-f1ef-4151-9bbe-005005aa3747",
"type": "customer"
}
},
"id": "d76265cd-0951-e511-80da-0aa34a9b2388",
"status": "processed",
"amount": {
"value": "42.00",
"currency": "usd"
},
"created": "2015-09-01T19:08:55.500Z"
}
```
```ruby get_transfer_status.rb theme={"dark"}
transfer_url = 'https://api.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388'
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
transfer = app_token.get transfer_url
transfer.status # => "processed"
```
```javascript get_transfer_status.js theme={"dark"}
var transferUrl =
"https://api.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388";
dwolla.get(transferUrl).then(function (res) {
res.body.status; // => 'processed'
});
```
```python get_transfer_status.py theme={"dark"}
transfer_url = 'https://api.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388'
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
fees = app_token.get(transfer_url)
fees.body['status'] # => 'processed'
```
```php get_transfer_status.php theme={"dark"}
byId($transferUrl);
print($transfer->status); # => "processed"
?>
```
That's it! You've successfully transferred money to a recipient. Please continue to the [Webhooks guide](/docs/working-with-webhooks) for information on implementing notifications for your customers about the status of the transfer.
# Testing in the Sandbox
Source: https://developers.dwolla.com/docs/testing
Test and refine your integration in the Dwolla Sandbox—a free, full-featured environment that simulates real API interactions, allowing you to build, experiment, and validate your application before going live.
## Overview
The Sandbox environment is a complete replica of the Dwolla production environment, supporting all of the same API endpoints. Applications should be tested against the Sandbox environment before being used in production.
### Differences from production environment
* The Sandbox contains only test data and is completely separate from your production account.
* All API endpoints have a base URL of `https://api-sandbox.dwolla.com/` instead of `https://api.dwolla.com/`
* Actual money is not sent or received as part of test transactions.
Real financial data should never be used in the Sandbox.
### Transfer behavior in the Sandbox
Unlike transfers that are sourced from a [Dwolla balance](/docs/balance-funding-source), which are processed instantaneously, bank-sourced transfers exist in the pending state for a few business days until they are `processed`, `failed`, or `cancelled`.
The Sandbox environment does not replicate any ACH processes, so a `pending` transfer will not clear or fail automatically after a few business days as it would in production. It will simply remain in the `pending` state indefinitely. Reference the [testing transfers](#testing-transfers) section for more information on how to simulate bank transfer processing in the Sandbox environment.
### Sandbox account setup
To set up your Sandbox account, all you will need is a valid email address. Once you agree to the Dwolla Developer Terms and Service, you will receive an email asking to verify your email address.
Failure to verify your email will result in a 401 HTTP status for all API
calls with an error code of InvalidAccountStatus.
After email verification, your Sandbox account will be created and you'll be redirected to our Sandbox Dashboard at `https://dashboard-sandbox.dwolla.com/`. Here you can view your API key and secret and generate an OAuth access token. Dwolla will also create an application for your account, associate a funding source named 'Superhero Savings Bank', and add \$5000 to the account balance for testing.
Start testing with a sandbox account
# Testing Customers
### Manage Customers in the Dashboard
The [Sandbox Dashboard](https://dashboard-sandbox.dwolla.com) allows you to manage Customers, as well as transfers associated with the Customers that belong to your Sandbox account. Once your application has [created its Customers](/docs/api-reference/customers/create-a-customer), you can access the [Sandbox Dashboard](https://dashboard-sandbox.dwolla.com) to validate that the request was recorded properly in our test environment.
There are multiple Customer types within the Dwolla API. Use our concept article for a more in-depth overview of each Customer type and its capabilities.
### Simulate identity verification statuses
There are various reasons a [Verified Customer](/docs/customer-types#verified-customer) may have a status other than `verified` after the initial Customer creation. You will want your app to be prepared to handle these alternative statuses.
In production, Dwolla will place the Verified Customer in either the `retry`, `kba`, `document`, `verified`, or `suspended` state of verification after an initial identity verification check.
**For personal Verified Customers**: Reference the guide on [customer verification](/docs/personal-verified-customer) for more information on handling identity verification for Verified Customers. To simulate the various statuses in the Sandbox, supply either `verified`, `retry`, `kba`, `document`, or `suspended` in the **firstName** parameter in order to [create a new Verified Customer](/docs/api-reference/customers/create-a-customer) with that status.
**For business Verified Customers**: Reference the guide on [customer verification](/docs/business-verified-customer) that goes over information on properly verifying a business's Controller, the business, and associated Beneficial Owners.
Here's how to simulate the different statuses and verification links for business Verified Customers in Sandbox:
`retry` status:
* For the business - Supply `retry` in the **businessName** parameter. This action will return a `retry-verification` link in the Customer resource.
* For both the Controller and business - Supply `retry` in the **controller firstName** parameter. This action will return both a `retry-verification` link and a `retry-with-full-ssn` link in the Customer resource.
`document` status:
* For the controller - Supply `document` in the **controller firstName** parameter. This action will return a `verify-with-document` linkin the Customer resource.
* For the business - Supply `document` in the **businessName** parameter. This action will return a `verify-business-with-document` link in the Customer resource.
* For both the Controller and the business - Submit `document` in both the **controller firstName** and the **businessName** parameters. This action will return a `verify-controller-and-business-with-document` link in the Customer resource.
`suspended`:
* Supply `suspended` in the **controller firstName** parameter to create a new Verified Customer with that status.
**For beneficial owners**:
To simulate different verification statuses for Beneficial Owners, submit either `incomplete` or `document` in the **beneficial owner firstName** parameter.
### Simulate KBA verified and failed events
If a Personal Verified Customer isn't systematically identity-verified after their second attempt to retry their information, the Customer may be placed in a `kba` status and will be required to successfully answer at least three out of four knowledge based authentication (KBA) questions in order to pass verification.
More information on KBA status for Personal Verified Customers and the related endpoints.
To simulate the `customer_kba_verification_passed` event as the result of KBA success in Sandbox, answer all four questions with either "None of the above" or "I have never been associated with this vehicle". As a result, the Customer will be placed in a verified status and the `customer_verified` event is triggered.
To simulate the `customer_kba_verification_failed` event as the result of KBA failure in Sandbox, answer the questions with any answer choices other than "None of the above" or "I have never been associated with this vehicle". As a result, the Customer will be placed in a document status and the `customer_verification_document_needed` event is triggered.
### Simulate document upload approved and failed events
If a Verified Customer isn't systematically identity-verified, the Customer may be placed in a `document` status and will require an identifying document to be uploaded and reviewed. Reference either the [personal customer verification](/docs/personal-verified-customer#document-types) or [business customer verification](/docs/business-verified-customer#document-types) guide for acceptable forms of identifying documents for `Verified Customers`.
Since the document review process requires interaction from Dwolla, sample test documents can be uploaded in the Sandbox environment to simulate the `customer_verification_document_approved` and `customer_verification_document_failed` events.
When downloading a test image, make sure to keep the size, format, and name of
the image the same.
#### **Sample document approved image**
#### **Sample document failed image**
### Simulate verification directives
You can test how your application handles specific embedded error codes (Verification Directives) in the Sandbox environment using the `/sandbox-simulations` endpoint. This allows you to simulate the Dwolla API returning a particular verification directive for a business Verified Customer.
This simulation can only be performed for customers that are in either `retry` or `document` status.
Learn more about the structure and meaning of these directives in the Understanding Verification Directives section.
The following error codes can be triggered:
* **PersonalIDRequired**
* **POBoxNotAllowed**
* **AddressNotAssociatedWithBusiness**
* **EINDocumentRequired**
To simulate one of these error codes:
1. Create a business Verified Customer in the Sandbox.
2. Make a POST request to the `/sandbox-simulations` endpoint with a request body like the following:
```bash HTTP theme={"dark"}
POST https://api-sandbox.dwolla.com/sandbox-simulations
Accept: application/vnd.dwolla.v1.hal+json
Content-Type: application/vnd.dwolla.v1.hal+json
Authorization: Bearer {Your access token}
{
"type": "customer-verification",
"_links": {
"customer": {
"href": "https://api-sandbox.dwolla.com/customers/{customer-id}"
}
},
"errorCode": "AddressNotAssociatedWithBusiness"
}
```
```ruby RUBY theme={"dark"}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
request_body = {
:type => "customer-verification",
:_links => {
:customer => {
:href => "https://api-sandbox.dwolla.com/customers/{customer-id}"
}
},
:errorCode => "AddressNotAssociatedWithBusiness"
}
simulation = app_token.post "sandbox-simulations", request_body
```
```python PYTHON theme={"dark"}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
request_body = {
'type': 'customer-verification',
'_links': {
'customer': {
'href': 'https://api-sandbox.dwolla.com/customers/{customer-id}'
}
},
'errorCode': 'AddressNotAssociatedWithBusiness'
}
simulation = app_token.post('sandbox-simulations', request_body)
```
```javascript JAVASCRIPT theme={"dark"}
var requestBody = {
type: "customer-verification",
_links: {
customer: {
href: "https://api-sandbox.dwolla.com/customers/{customer-id}"
}
},
errorCode: "AddressNotAssociatedWithBusiness"
};
dwolla.post("sandbox-simulations", requestBody);
```
```php PHP theme={"dark"}
create([
'type' => 'customer-verification',
'_links' => [
'customer' => [
'href' => 'https://api-sandbox.dwolla.com/customers/{customer-id}'
]
],
'errorCode' => 'AddressNotAssociatedWithBusiness'
]);
?>
```
Replace `{customer-id}` with the ID of your Sandbox customer, and set `errorCode` to one of the supported values (e.g., `PersonalIDRequired`, `POBoxNotAllowed`, `AddressNotAssociatedWithBusiness`, or `EINDocumentRequired`).
After making this request, retrieve the Customer resource using the API. The specified error code will appear in the `_embedded.errors` array of the response, allowing you to test your application's handling of these verification directives.
# Testing Funding Sources
### Test bank account numbers
Dwolla requires a valid U.S. routing number and a random account number between 4-17 digits to add a bank account. For testing purposes, you can use the routing number `222222226` or refer to the list of routing numbers from the [Federal Reserve Bank Services](https://www.frbservices.org/EPaymentsDirectory/agreement.html) website.
### Test micro-deposit verification
If your application leverages the micro-deposit method of bank verification, Dwolla will transfer two deposits of less than `$0.10` to your customer's linked bank or credit union account after calling the API to initiate micro-deposits. Since the Sandbox environment doesn't replicate any bank transfer processes, any two amounts **below** `$0.10` will allow you to verify the funding source immediately. In Production, when the micro-deposits have finished processing, you will receive a `customer_microdeposits_completed` event. To trigger this event in Sandbox, you need to simulate bank transfer processing. Check out the [testing transfers](#testing-transfers) section for more information on how to simulate bank transfer processing in Sandbox.
Use our step-by-step guide to verify a funding-source with micro-deposits.
### Test micro-deposit failed verification attempts
When verifying a funding source using the micro-deposit method of bank verification, users are allowed **three attempts** to correctly input the two posted micro-deposit amounts. If the user fails to verify the two posted amounts on the third attempt, an event will be triggered and the funding source will not be verified using those micro-deposit amounts. To simulate the `microdeposits_maxattempts` or `customer_microdeposits_maxattempts` events in the Sandbox, use the amounts `0.09` and `0.09` when calling the API to verify micro-deposits. Reference the [micro-deposit verification](/docs/micro-deposit-verification#handle-failed-verification-attempts) guide for more information on handling failed verification attempts.
# Testing Transfers
The Sandbox environment does not replicate any bank transfer processes, so a pending transfer will not clear or fail automatically after a few business days as it would in production. The transfer will simply remain in the pending state indefinitely.
### Simulate bank transfer processing
There are two options available for processing or failing bank transfers in the Sandbox environment.
* **Option 1:** your application will call the "sandbox-simulations" endpoint (referenced below) which will process or fail the last 500 bank transfers that occurred on the authorized application or Sandbox account.
* **Option 2:** you'll use the "Process bank transfers button" in the Sandbox Dashboard, which will process or fail the last 500 bank transfers that occurred on your Sandbox account or any API `Customers` you manage.
If a bank-to-bank transaction is initiated between two users, you'll want to
simulate bank transfer processing twice in order to process both sides of the
transaction (debit and credit). Processing for bank transfers will also
include initiated micro-deposits. If your application is subscribed to webhooks, notifications will be sent, including all transfer or micro-deposit related events,
letting your application know that transfers have processed or failed.
#### Sandbox simulations request and response
```bash theme={"dark"}
POST https://api-sandbox.dwolla.com/sandbox-simulations
Accept: application/vnd.dwolla.v1.hal+json
Content-Type: application/vnd.dwolla.v1.hal+json
Authorization: Bearer {Your access token}
...
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/sandbox-simulations",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "sandbox-simulation"
}
},
"total": 8
}
```
### Process bank transfers button
The [Dwolla Sandbox Dashboard](https://dashboard-sandbox.dwolla.com) provides a convenient way to simulate bank transfer processing. In the left-side navigation, locate and click the "Process bank transfers" button.
This button offers the same functionality as the "sandbox-simulations" endpoint (mentioned earlier) and allows you to simulate the processing of bank transfers within the Sandbox environment.
By clicking the button, Dwolla will process or fail the last 500 bank transfers associated with your Sandbox account or any API Customer accounts you manage.
### Test bank transfer failures
Transfers to or from a bank account can fail for a number of reasons (e.g. insufficient funds,
invalid account number, etc.). When a bank transfer fails, the associated financial institution that
rejected the transaction assigns an ACH return code and a transfer failure event is then triggered
in Dwolla. Dwolla allows you to trigger various bank transfer failures by specifying an “R” code in
the funding source `name` parameter when creating or
[updating a funding source](https://developers.dwolla.com/api-reference/funding-sources/update) for
a Dwolla Account or API Customer. When a
[transfer is initiated](https://developers.dwolla.com/api-reference/transfers/initiate) using a
funding source that has an “R” code assigned to its name, a transfer failure event will trigger and
the status will update to failed when you simulate bank transfer processing (as mentioned above).
Dwolla allows you to pass in a few different sentinel values that are used to test different bank
transfer failure scenarios. The list of available sentinel values cover the most common uses cases
where ACH return codes can be triggered in production.
#### List of codes for testing bank transfer failures
| Return code | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| R01 | Insufficient Funds: This value is used to simulate funds failing from the source bank account (ACH debit). |
| R03 | No Account/Unable to Locate Account: This value is primarily used to simulate funds failing to the destination bank account (ACH credit). The funding source will be automatically removed from Dwolla when this return code is triggered. |
| R01-late | This value is used to simulate funds failing from the source bank account post-settlement. Note: You must click “Process bank transfers” twice in order to test this scenario. |
| R03-late | This value is primarily used to simulate funds failing to the destination bank post-settlement. The funding source will be automatically removed from Dwolla when this return code is triggered. Note: You must click “Process bank transfers” twice in order to test this scenario. |
Our concept article has more information on bank transfer failures, and a list of common return codes and actions.
#### Example of using a sentinel value for testing bank transfer failures
This example assumes that a funding source has already been attached to an account. Once the funding
source `name` has been updated to reflect the ACH failure scenario you want to test, then you can
[initiate a transfer](https://developers.dwolla.com/api-reference/transfers/initiate) to or from
that funding source via the API.
```bash HTTP theme={"dark"}
POST https://api-sandbox.dwolla.com/funding-sources/692486f8-29f6-4516-a6a5-c69fd2ce854c
Accept: application/vnd.dwolla.v1.hal+json
Content-Type: application/vnd.dwolla.v1.hal+json
Authorization: Bearer pBA9fVDBEyYZCEsLf/wKehyh1RTpzjUj5KzIRfDi0wKTii7DqY
...
{ "name": "R03" }
```
```ruby RUBY theme={"dark"}
funding_source_url = 'https://api-sandbox.dwolla.com/funding-sources/692486f8-29f6-4516-a6a5-c69fd2ce854c'
request_body = {
"name" => "R03",
}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
funding_source = app_token.post "#{funding_source_url}", request_body
funding_source.name # => "R03"
```
```php PHP theme={"dark"}
/**
* No example for this language yet. Coming soon.
**/
```
```python PYTHON theme={"dark"}
funding_source_url = 'https://api-sandbox.dwolla.com/funding-sources/692486f8-29f6-4516-a6a5-c69fd2ce854c'
request_body = {
'name': 'R03'
}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
funding_source = app_token.post(funding_source_url, request_body)
funding_source.body['name'] # => 'R03'
```
```javascript JAVASCRIPT theme={"dark"}
var fundingSourceUrl =
'https://api-sandbox.dwolla.com/funding-sources/692486f8-29f6-4516-a6a5-c69fd2ce854c';
var requestBody = {
name: 'R03',
};
dwolla.post(fundingSourceUrl, requestBody).then((res) => res.body.name); // => "R03"
```
# Testing Virtual Account Numbers
Virtual Account Numbers (VANs) allow you to simulate external ACH transactions flowing into and out of Dwolla balances. The Sandbox environment provides specialized endpoints for testing VAN functionality without real money movement.
To request Sandbox access to test VANs, please contact
[support@dwolla.com](mailto:support@dwolla.com).
### Simulate VAN transfers
External transfers can be simulated in the Dwolla Sandbox by using the `sandbox-simulations` endpoint with a request body that includes a `type` field set to `virtual` and a `transfers` field with a list of transfers to process. Up to 10 transfers at a time can be included in one call to the sandbox simulations endpoint. Transfers will be created and processed immediately.
#### VAN transfer simulation request
```bash theme={"dark"}
POST https://api-sandbox.dwolla.com/sandbox-simulations
Accept: application/vnd.dwolla.v1.hal+json
Content-Type: application/vnd.dwolla.v1.hal+json
Authorization: Bearer {Your access token}
{
"type": "virtual",
"transfers": [
{
"_link": {
"destination": {
"href": "https://api-sandbox.dwolla.com/funding-sources/5880e310-675a-4ce3-87d9-a475cc565e09"
}
},
"amount": {
"currency": "USD",
"value": "1.11"
}
}
]
}
...
HTTP/1.1 202 Accepted
```
### Test VAN transfer failures
Similar to [testing ACH bank transfer failures](#test-bank-transfer-failures), you can test a transfer failure with a virtual account number by specifying an "R" code in the `name` parameter when creating a VAN (e.g. "R01"). The return code must be at the beginning of the VAN funding source's name. **Note:** the name is not case sensitive.
#### List of codes for testing VAN transfer failures
| Code | Description | When is the failure triggered? |
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| R01 | Insufficient Funds: This value is used to simulate funds failing from the source bank account (ACH debit). | During processing of pending transactions. |
| R02 | No Account/Unable to Locate Account: This value is primarily used to simulate funds failing to the destination bank account (credit). The funding source will be automatically removed from Dwolla when this return code is triggered. | During processing of pending transactions. |
# Transfer Failures
Source: https://developers.dwolla.com/docs/transfer-failures
Familiarize yourself with the unhappy path of transfers and build resilient practices to mitigate ACH returns. Learn about returns, why they happen and recommended actions your application can take based on the return code.
## Overview
There are several reasons bank transfers can fail, a few of which are outlined below. When a transfer fails it is usually a result of an ACH failure which is assigned an ACH return code after being rejected from the financial institution. A few common failure examples include:
* **Insufficient Funds (R01):** Pending transfers can fail due to insufficient funds from the source bank account.
* **No Account/Unable to Locate Account (R03):** The recipient of a transfer has closed their bank account or has incorrectly entered their bank account/routing number when attaching their funding source.
* **Customer Advises Not Authorized (R10):** The owner of a bank account has told their bank that this transfer was unauthorized.
### Retrieving the transfer
You can check the status of a transfer at any time by [retrieving the transfer via the API](/docs/api-reference/transfers/retrieve-a-transfer). When a bank transfer is unable to be completed, its status will be updated to `failed`. The response from the API when retrieving the transfer should contain a `"failure"` link that your application will follow to [retrieve the transfer failure reason](/docs/api-reference/transfers/retrieve-a-transfer-failure-reason) in the next step.
##### Example failure link
```json theme={"dark"}
"failure": {
"href": "https://api-sandbox.dwolla.com/transfers/a1e58cd8-11ec-e811-8111-bec1f96924ed/failure",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "failure"
}
```
### Retrieving the reason for a failed bank transfer
If your application is subscribed to webhooks, you'll receive either the `transfer_failed` event if the transfer belongs to a Dwolla account or the `customer_transfer_failed`/`customer_bank_transfer_failed`(*Verified Customer only*) event if the transfer belongs to an API Customer. The event contains a links to the associated account as well as the transfer resource. When retrieving the failed bank transfer reason, the response will contain information on the ACH return code and description, as well as `_links` to the Funding Source and Customer that triggered the bank transfer failure.
##### Request and response
```bash HTTP theme={"dark"}
GET https://api-sandbox.dwolla.com/transfers/8997ebed-69be-e611-80ea-0aa34a9b2388/failure
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer pBA9fVDBEyYZCEsLf/wKehyh1RTpzjUj5KzIRfDi0wKTii7DqY
{
"_links": {
"self": {
"href": "https://api.dwolla.com/transfers/8997ebed-69be-e611-80ea-0aa34a9b2388/failure",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "failure"
},
"failed-funding-source": {
"href": "https://api.dwolla.com/funding-sources/285ea6f4-c45d-4e15-ad33-21f51461f437",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "funding-source"
},
"customer": {
"href": "https://api.dwolla.com/customers/be2d2322-fdee-4361-8722-4289f5601604",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "customer"
}
},
"code": "R03",
"description": "No Account/Unable to Locate Account",
"explanation": "The account number does not correspond to the individual identified in the entry or a valid account."
}
```
```ruby retrieve_transfer_failure.rb theme={"dark"}
transfer_url = 'https://api-sandbox.dwolla.com/transfers/8997ebed-69be-e611-80ea-0aa34a9b2388'
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby
failure = app_token.get "#{transfer_url}/failure"
failure.code # => "R01"
```
```php retrieve_transfer_failure.php theme={"dark"}
failureById($transfer);
print($failureReason->code); # => "R01"
?>
```
```python retrieve_transfer_failure.py theme={"dark"}
transfer_url = 'https://api-sandbox.dwolla.com/transfers/8997ebed-69be-e611-80ea-0aa34a9b2388'
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
failure = app_token.get('%s/failure' % transfer_url)
failure.body['code'] # => 'R01'
```
```javascript retrieveTransferFailure.js theme={"dark"}
var transferUrl =
"https://api-sandbox.dwolla.com/transfers/8997ebed-69be-e611-80ea-0aa34a9b2388";
dwolla.get(`${transferUrl}/failure`).then((res) => res.body.code); // => 'R01'
```
### What occurs in the Dwolla system when a bank transfer fails?
When a bank transfer failure occurs there is a subset of systematic actions Dwolla may take on the Customer and/or the funding source based on the ACH return code. It is recommended to have an active [webhook subscription](/docs/api-reference/webhook-subscriptions/create-a-webhook-subscription), which is used to listen for events relating to any Customer or funding source state change. Please refer to the table below to understand the systematic actions that Dwolla may take for Customer and funding source resources, as well as the events that are created.
#### Systematic actions taken against the Customer
| Customer action | Description | Webhook Event |
| --------------- | -------------------------------------------------------------------------- | ---------------------- |
| None | No action taken against the Customer account as result of transfer failure | N/A |
| Deactivated | Customer account has been deactivated | `customer_deactivated` |
| Suspended | Customer account has been suspended (R10 only) | `customer_suspended` |
#### Systematic actions taken against the bank funding source
| Funding Source action | Description | Webhook Event |
| --------------------- | -------------------------------------------------------- | ------------------------------------ |
| None | No action taken against the Customer bank funding source | N/A |
| Unverified | A Customer's bank has been unverified, but not removed | `customer_funding_source_unverified` |
| Removed | A Customer's bank has been removed | `customer_funding_source_removed` |
### Why does Dwolla automatically take these actions?
Being able to catch and take action errors can be beneficial on many levels. For instance, if your Customer initiates a transaction which fails with an R10 (Customer Advises Not Authorized) return code, Dwolla will automatically put the suspected Customer in a `suspended` status, thereby not allowing them to initiate or receive more transfers. This gives you the ability investigate the Customer to determine if they are a valid party without worrying about them sending funds. Other return codes may result in the Customer being `deactivated`, or may only affect bank funding sources in which Dwolla may automatically unverify or remove a bank in response to various return codes.
### List of possible return codes, descriptions, and actions
Below are tables of the most common return codes we see involved in transactions, organized by category. For a full list of return codes, you can check out the ACH return code [list on our blog](https://www.dwolla.com/ach/ach-return-codes/).
As a best practice, we recommend handling any systematic actions that trigger
webhooks as a result of a transfer failure rather than relying on the specific
actions as referenced below. We do not recommend building a workflow around
each individual return code in the tables below. These tables are solely meant to be
a reference for you to be aware of actions Dwolla may take on common transfer
failures.
#### Administrative Returns
Administrative returns occur when there's an issue with the account itself—such as insufficient funds, a closed account, or invalid account information. These are typically returned within **2 banking days** and generally don't indicate fraudulent activity.
| Code | Reason & Description | Return Time Frame | Triggered Action(s) |
| ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| R01 | **Insufficient Funds**
Available balance is not sufficient to cover the dollar value of the debit entry. | 2 banking days | None |
| R02 | **Bank Account Closed**
Previously active account has been closed. | 2 banking days | - [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
|
| R03 | **No Account/Unable to Locate Account**
Account number structure is valid, but does not match individual identified in entry or is not an open account. | 2 banking days | - [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
|
| R04 | **Invalid Bank Account Number Structure**
Account number structure is not valid. | 2 banking days | - [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
|
| R08 | **Payment Stopped**
The Receiver has requested the stop payment of a specific ACH debit entry. | 2 banking days | - [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
|
| R09 | **Uncollected Funds**
Sufficient balance exists, but value of uncollected items brings available balance below amount of debit entry. | 2 banking days | None |
| R12 | **Branch Sold to Another DFI**
A financial institution received an entry to an account that was sold to another FI (typically due to a merger). | 2 banking days | - [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
|
| R13 | **Invalid ACH Routing Number**
Entry contains a receiving DFI identification or gateway identification that is not a valid ACH routing number. | Next file delivery | - [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
| R14 | **Representative Payee Deceased or Unable to Continue in That Capacity**
The representative payee is a person either deceased or no longer able to continue in original capacity (i.e. legally incapacitated adults or minors), while the beneficiary is not deceased. | 2 banking days | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
|
| R15 | **Beneficiary or Account Holder Deceased**
(1) The beneficiary is deceased, or (2) The account holder is deceased. | 2 banking days | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
| R16 | **Account Frozen/Entry Returned per OFAC Instruction**
(1) Access to the account is restricted due to specific action taken by the RDFI or by legal action; or (2) OFAC has instructed the RDFI to return the entry. | 2 banking days | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
| R17 | **File Record Edit Criteria/Entry with Invalid Account Number Initiated Under Questionable Circumstances**
(1) Field(s) cannot be processed by RDFI; or (2) The entry contains an invalid DFI Account Number (account closed/no account/unable to locate account/invalid account number) and is believed by the RDFI to have been initiated under questionable circumstances. | 2 banking days | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
| R20 | **Non-Transaction Account**
ACH entry to a non-transaction account (typically due to account holder exceeding their monthly withdrawal threshold under Regulation D). | 2 banking days | - [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
| R22 | **Invalid Individual ID Number**
The Receiver has indicated to the RDFI that the number with which the Originator identified is not correct. | 2 banking days | - [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
|
| R23 | **Credit Entry Refused by Receiver**
Any credit entry that is refused by the Receiver may be returned by the RDFI. | 2 banking days | - [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
|
#### Unauthorized Returns
Unauthorized returns occur when the account holder claims they did not authorize the transaction. These are more serious and can be returned up to **60 calendar days** after the settlement date. Due to the nature of these returns, Dwolla takes protective actions on the Customer account.
Unauthorized returns may indicate potential fraud or disputes. Dwolla automatically suspends or deactivates the Customer to protect against further unauthorized activity.
| Code | Reason & Description | Return Time Frame | Triggered Action(s) |
| ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| R05 | **Unauthorized Debit to Consumer Account Using Corporate SEC Code**
A CCD or CTX debit entry was transmitted to a consumer account and was not authorized by the Receiver. Written Statement is required. | 60 calendar days | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
| R07 | **Authorization Revoked by Customer**
Consumer who previously authorized entries has revoked authorization with the Originator. Written Statement is required. | 60 calendar days | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
|
| R10 | **Customer Advises Originator is Not Known to Receiver and/or Originator is Not Authorized by Receiver to Debit Receiver's Account**
Receiver has no relationship with the Originator or has not authorized the Originator to debit the account. Written Statement is required. | 60 calendar days | - [Customer Suspended](/docs/webhook-events#param-customer-suspended)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
| R11 | **Customer Advises Entry Not in Accordance with the Terms of the Authorization**
The debit entry was inaccurate or improperly initiated. Other reasons include source document was ineligible, notice was not provided to the receiver or amount was inaccurately obtained. Written statement is required. | 60 calendar days | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
|
| R29 | **Corporate Customer Advises Not Authorized**
The RDFI has been notified by the Receiver (non-consumer) that a specific entry has not been authorized by the Receiver. | 2 banking days | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
#### General Returns
These returns are initiated by the originating or receiving financial institution, or relate to specific transaction types like source documents or permissible returns. Return time frames vary.
| Code | Reason & Description | Return Time Frame | Triggered Action(s) |
| ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| R06 | **Returned per ODFI's Request**
The ODFI has requested that the RDFI return an erroneous entry, or a credit entry originated without the authorization of the Originator. | Varies | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
| R31 | **Permissible Return (CCD and CTX only)**
The RDFI may return a CCD or CTX entry that the ODFI agrees to accept. | Varies | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
| R37 | **Source Document Presented for Payment**
Source document to which an ARC, BOC, or POP entry relates has been presented for payment. Written Statement is required. | 60 calendar days | - [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
|
| R38 | **Stop Payment on Source Document**
The RDFI indicates a stop payment order has been placed on the source document the ARC or BOC entry relates to. | 60 calendar days | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
| R51 | **Item Related to RCK Entry is Ineligible or RCK Entry is Improper**
The RDFI notifies that the RCK entry is considered ineligible or improper. Written Statement is required. | 60 calendar days | - [Customer Deactivated](/docs/webhook-events#param-customer-deactivated)
- [Funding Source Unverified](/docs/webhook-events#param-customer-funding-source-unverified)
- [Funding Source Removed](/docs/webhook-events#param-customer-funding-source-removed)
- Funding Source Blocklisted
|
# Transfer Lifecycle
Source: https://developers.dwolla.com/docs/transfer-lifecycle
Learn about the complete lifecycle of a transfer in Dwolla and find out what webhooks you can expect to receive during each step.
## Overview
To initiate a transfer, you can either make an API request or use the Dwolla Dashboard. You will
need to specify the source and destination funding sources, which can be either a
[bank account](/docs/bank-funding-source) or a [Dwolla Wallet](/docs/balance-funding-source).
It's important to note that all account-to-account transfers are two-sided. When a transfer request
is received, it is broken down into "hops" or "legs," which describe each step of the process, such
as pulling funds into a Dwolla Wallet and pushing funds out of it.
## Transfer Statuses
As outlined in our [transfer resource](/docs/api-reference/transfers), transfers can have the
following statuses: `pending`, `processed`, `cancelled`, or `failed`.
* `pending` - A pending transfer hasn't been sent to the payment network or has been sent but not
processed. This means that it may still be cancellable or may result in a transfer failure.
* `processed` - The meaning of a "processed" status varies based on the transfer destination. If
it's going to a Dwolla Wallet, the funds have cleared successfully. If it's going to a linked bank
account, enough time has passed for the funds to clear into that account.
* `cancelled` - A transfer can be cancelled in two ways: either Dwolla cancels it systematically, or
your application sends an API request. If a funding source is removed during the transfer's
journey to that bank account, Dwolla will cancel the transfer.
* `failed` - A failed status is associated with an ACH network return, meaning that Dwolla received
an ACH return code from the RDFI (Receiving Depository Financial Institution). You can fetch the
failure reason via an additional API request.
[Learn more on transfer failures](/docs/transfer-failures).
## Flow of Funds
It's crucial to understand how funds flow into and out of the Dwolla Network because all transfers
require at least one user (either the sender or receiver) to hold a balance in a Dwolla Wallet. In
other words, **all transfers flow through a Dwolla Wallet, either explicitly or implicitly**.
* With an explicit flow of funds, the source of the transfer is a bank account (funding source) and
the destination is a Dwolla Wallet, or vice versa. In this scenario, the funds are explicitly
pushed into or pulled out of a Dwolla Wallet when the transfer is initiated.
* With an implicit flow of funds, the source and destination of the transfer are both bank accounts
(funding sources). In this scenario, the funds first enter the Dwolla Network by flowing through a
Dwolla Wallet of either the sender or receiver (or both, depending on the customer type), before
ultimately exiting to the destination bank account.
## Tracking Transfers
As mentioned previously, all account-to-account payments are two-sided. This can make it difficult
to track the status of a transfer at any given time. To address this, Dwolla supports two ways to
track transfers throughout their lifecycle:
* **Correlation ID**: This value is supplied by your application when the transfer is created, and
you can use it to track the transfer through the Dwolla API.
* **Response JSON `_links` property**: This property is automatically populated by Dwolla in the
response JSON for a transfer. It contains links to other resources that you can use to track the
transfer, such as the transfer's status and the transfer's history.
### Correlation ID
Many Dwolla resources allow a correlation ID—[Customers](/docs/api-reference/customers),
[Mass Payments](/docs/api-reference/mass-payments), and [Transfers](/docs/api-reference/transfers).
Through correlation IDs your application can send an ID of your choosing (generally a pseudo-random
alphanumeric string) that is attached to the resource once it’s created in the Dwolla system. In
particular, transfers are easily traceable in our API, as our transfers endpoint allows listing and
searching based on a `correlationId` value.
#### Create Transfer with Correlation ID
```javascript createTransfer.js theme={"dark"}
// Using dwolla-v2 - https://github.com/Dwolla/dwolla-v2-node
const requestBody = {
_links: {
source: {
href: "https://api.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4",
},
destination: {
href: "https://api.dwolla.com/funding-sources/ab443d36-3757-44c1-a1b4-29727fb3111c",
},
},
amount: {
currency: "USD",
value: "10.00",
},
correlationId: "8a2cdc8d-629d-4a24-98ac-40b735229fe2",
};
dwolla .post("transfers", requestBody) .then((res) => res.headers.get("Location")); // =>
'https://api.dwolla.com/transfers/74c9129b-d14a-e511-80da-0aa34a9b2388'
```
```php create_transfer.php theme={"dark"}
create([
'_links' => [
'source' => [
'href' => 'https://api.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4',
],
'destination' => [
'href' => 'https://api.dwolla.com/funding-sources/ab443d36-3757-44c1-a1b4-29727fb3111c'
]
],
'amount' => [
'currency' => 'USD',
'value' => '10.00'
],
'correlationId' => '8a2cdc8d-629d-4a24-98ac-40b735229fe2'
]);
$transfer; # => "https://api.dwolla.com/transfers/74c9129b-d14a-e511-80da-0aa34a9b2388"
```
```ruby create_transfer.rb theme={"dark"}
# Using dwolla_v2 - https://github.com/Dwolla/dwolla-v2-ruby
request_body = {
:_links => {
:source => {
:href => "https://api.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4"
},
:destination => {
:href => "https://api.dwolla.com/funding-sources/ab443d36-3757-44c1-a1b4-29727fb3111c"
}
},
:amount => {
:currency => "USD",
:value => "10.00"
},
:correlationId => "8a2cdc8d-629d-4a24-98ac-40b735229fe2"
}
transfer = app_token.post "transfers", request_body
transfer.response_headers[:location] # => "https://api.dwolla.com/transfers/74c9129b-d14a-e511-80da-0aa34a9b2388"
```
```python create_transfer.py theme={"dark"}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python
request_body = {
'_links': {
'source': {
'href': 'https://api.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4'
},
'destination': {
'href': 'https://api.dwolla.com/funding-sources/ab443d36-3757-44c1-a1b4-29727fb3111c'
}
},
'amount': {
'currency': 'USD',
'value': '10.00'
},
'correlationId': '8a2cdc8d-629d-4a24-98ac-40b735229fe2'
}
transfer = app_token.post('transfers', request_body)
transfer.headers['location'] # => 'https://api.dwolla.com/transfers/74c9129b-d14a-e511-80da-0aa34a9b2388'
```
```bash HTTP theme={"dark"}
POST https://api.dwolla.com/transfers
Accept: application/vnd.dwolla.v1.hal+json
Content-Type: application/vnd.dwolla.v1.hal+json
Authorization: Bearer pBA9fVDBEyYZCEsLf/wKehyh1RTpzjUj5KzIRfDi0wKTii7DqY
Idempotency-Key: 19051a62-3403-11e6-ac61-9e71128cae77
{
"_links": {
"source": {
"href": "https://api.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4"
},
"destination": {
"href": "https://api.dwolla.com/funding-sources/ab443d36-3757-44c1-a1b4-29727fb3111c"
}
},
"amount": {
"currency": "USD",
"value": "10.00"
},
"correlationId": "8a2cdc8d-629d-4a24-98ac-40b735229fe2"
}
...
HTTP/1.1 201 Created
Location: https://api.dwolla.com/transfers/74c9129b-d14a-e511-80da-0aa34a9b2388
```
#### List (Search) Customer Transfers via Correlation ID
```javascript searchTransfer.js theme={"dark"}
// Using dwolla-v2 - https://github.com/Dwolla/dwolla-v2-node
const customerUrl =
"https://api.dwolla.com/customers/33e56307-6754-41cb-81e2-23a7f1072295";
dwolla .get( `${customerUrl}/transfers?correlationId=8a2cdc8d-629d-4a24-98ac-40b735229fe2` )
.then((res) => res.body.\_embedded["transfers"][0].status); // => "pending"
```
```php search_transfer.php theme={"dark"}
getCustomerTransfers($customerUrl);
$transfers->_embedded->{'transfers'}[0]->status; # => "pending"
```
```ruby search_transfer.rb theme={"dark"}
# Using dwolla_v2 - https://github.com/Dwolla/dwolla-v2-ruby
customer_url = 'https://api.dwolla.com/customers/33e56307-6754-41cb-81e2-23a7f1072295'
transfers = app_token.get "#{customer_url}/transfers?correlationId=8a2cdc8d-629d-4a24-98ac-40b735229fe2"
transfers._embedded['transfers'][0].status # => "pending"
```
```python search_transfer.py theme={"dark"}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python
customer_url = 'https://api.dwolla.com/customers/33e56307-6754-41cb-81e2-23a7f1072295'
transfers = app_token.get('%s/transfers?correlationId=%s' % customer_url % '8a2cdc8d-629d-4a24-98ac-40b735229fe2')
transfers.body['_embedded']['transfers'][0]['status'] # => 'pending'
```
```bash HTTP theme={"dark"}
GET https://api.dwolla.com/customers/33e56307-6754-41cb-81e2-23a7f1072295/transfers?correlationId=8a2cdc8d-629d-4a24-98ac-40b735229fe2
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer pBA9fVDBEyYZCEsLf/wKehyh1RTpzjUj5KzIRfDi0wKTii7DqY
...
{
"_links": {
"self": {
"href": "https://api.dwolla.com/customers/33e56307-6754-41cb-81e2-23a7f1072295/transfers"
},
"first": {
"href": "https://api.dwolla.com/customers/33e56307-6754-41cb-81e2-23a7f1072295/transfers?&limit=25&offset=0"
},
"last": {
"href": "https://api.dwolla.com/customers/33e56307-6754-41cb-81e2-23a7f1072295/transfers?&limit=25&offset=0"
}
},
"_embedded": {
"transfers": [
{
"_links": {
"self": {
"href": "https://api.dwolla.com/transfers/74c9129b-d14a-e511-80da-0aa34a9b2388"
},
"source": {
"href": "https://api.dwolla.com/customers/39e21228-5958-4c4f-96fe-48a4bf11332d"
},
"source-funding-source": {
"href": "https://api.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4"
},
"destination": {
"href": "https://api.dwolla.com/customers/33e56307-6754-41cb-81e2-23a7f1072295"
},
"destination-funding-source": {
"href": "https://api.dwolla.com/funding-sources/ab443d36-3757-44c1-a1b4-29727fb3111c"
}
},
"id": "74c9129b-d14a-e511-80da-0aa34a9b2388",
"status": "pending",
"amount": {
"value": "10.00",
"currency": "USD"
},
"created": "2018-11-29 21:00:59 UTC",
"correlationId": "8a2cdc8d-629d-4a24-98ac-40b735229fe2"
}
]
},
"total": 1
}
```
### Response JSON Links
In addition to using a correlation ID, Dwolla automatically appends two resource `_links` properties
in all transfers: funding-transfer and funded-transfer, both of which can be used to traverse
programmatically a single transfer chain.
* `funding-transfer`: A resource link that points to the previous transfer in the overall chain (if
applicable). It identifies the previous transfer that, once processed, funded the current
transfer.
* `funded-transfer`: A resource link that points to the next transfer in the overall chain (if
applicable). It identifies the next transfer that the current transfer will fund, once processed.
**Example**
Consider a successful account-to-account transfer where a Verified Customer (VCR-1) sends money from
their bank account to the bank account of another Verified Customer (VCR-2). The “legs” of the
overall transfer lifecycle would look like this: Verified Customer (VCR-1) Bank to Verified Customer
(VCR-2) Bank
| Name | Transfer ID | Resource Links |
| ----------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| VCR-1 Bank → VCR-1 Balance | a379c863-77f2-4248-8b37-486bc10b3817 | `funding-transfer`: N/A
`funded-transfer`: 5dc959dd-29ec-436f-8cdc-dd7269fd4e7c |
| VCR-1 Balance → VCR-2 Balance | 5dc959dd-29ec-436f-8cdc-dd7269fd4e7c | `funding-transfer`: a379c863-77f2-4248-8b37-486bc10b3817
`funded-transfer`: a91f21c1-d9bb-41a6-8aab-981e763bd325 |
| VCR-2 Balance → VCR-2 Bank | a91f21c1-d9bb-41a6-8aab-981e763bd325 | `funding-transfer`: 5dc959dd-29ec-436f-8cdc-dd7269fd4e7c
`funded-transfer`: N/A |
* The `funding-transfer` link for the first transfer is N/A because there is no previous transfer in
the chain.
* The `funded-transfer` link for the first transfer points to the second transfer, since the funds
from the first transfer funded the second transfer.
* The `funding-transfer` link for the second transfer points to the first transfer, which is the
transfer that funded it.
* The `funded-transfer` link for the third transfer is N/A because there are no further transfers in
the chain.
## Interactive Transfer Lifecycle
# Transfer Money Between Users
Source: https://developers.dwolla.com/docs/transfer-money-between-users
Facilitate ACH transfers between two distinct parties, e.g. for marketplace applications that connect buyers with sellers for bank to bank payments.
## Overview
The most common scenario for this guide is to facilitate marketplace or peer-to-peer transfers between your customers.
In this guide, we'll cover the key points of transferring money:
Create a Verified Customer in your application to act as the recipient of the funds.
Create an Unverified Customer who will initiate and send the funds.
Link and verify a bank or credit union account to the sender's profile to enable sending funds.
Link a bank or credit union account to the recipient's profile (verification not required for receiving funds).
Initiate a transfer from the sender's verified funding source to the recipient's funding source using the Dwolla API.
Certain use cases involving transfers between users, such as peer-to-peer payments, may require additional review to ensure alignment with Dwolla's risk policies. Before you start building, contact our Sales team to confirm that your use case is supported.
## Before you begin
You need to have a [Sandbox account](/docs/testing) already set up.
## Verified and Unverified Customers
Here are some rules to keep in mind:
1. With a transfer of money, at least one party must complete the [identity verification process](https://www.dwolla.com/updates/guide-customer-identification-program-payments-api/), either the sender or the receiver. It's your decision about which party completes this process, based on your business model, and you may want to have both parties complete the identity verification process.
2. The sender must have a verified funding source. Unverified funding sources can only receive money, not send.
In this guide, we'll create two Customers: one to represent a seller and one to represent a buyer. In this scenario, the seller, Jane Merchant, is a `Verified Customer` with an unverified funding source. The buyer, Joe Buyer, is an `Unverified Customer` with a verified funding source.
This is a suggested approach and there are other ways you can implement your
marketplace transfers. For instance, both the sender and the receiver (or
buyer and seller) could be Verified Customers, and both could
have verified funding sources. Or, you could have the sender undergo identity
verification but not the recipient.
Looking to learn more about each Customer type and how it relates to your funds flow? Take a look at our [Customer types](/docs/customer-types) article for more information.
# Step 1 - Create a Verified Customer
First, we'll create a `Verified Customer` for Jane Merchant.
There are two types of Verified Customers you can create; [Personal Verified Customers](/docs/personal-verified-customer) and [Business Verified Customers.](/docs/business-verified-customer) In this example, we use [Business Verified Customers](/docs/business-verified-customer) (sole proprietorship) to represent the merchant who will be receiving funds.
```bash HTTP theme={"dark"}
POST https://api-sandbox.dwolla.com/customers
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNic+oWhDbQcVSKLRUpGjIdl/YyrHqrDDoRnQwE7Q
{
"firstName": "Jane",
"lastName": "Merchant",
"email": "solePropBusiness@email.com",
"ipAddress": "143.156.7.8",
"type": "business",
"dateOfBirth": "1980-01-31",
"ssn": "6789",
"address1": "99-99 33rd St",
"city": "Some City",
"state": "NY",
"postalCode": "11101",
"businessClassification": "9ed3f670-7d6f-11e3-b1ce-5404a6144203",
"businessType": "soleProprietorship",
"businessName":"Jane Corp",
"ein":"00-0000000"
}
HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/customers/62c3aa1b-3a1b-46d0-ae90-17304d60c3d5
```
```php create_verified_customer.php theme={"dark"}
create([
'firstName' => 'Jane',
'lastName' => 'Merchant',
'email' => 'solePropBusiness@email.com',
'ipAddress' => '143.156.7.8',
'type' => 'business',
'dateOfBirth' => '1980-01-31',
'ssn' => '6789',
'address1' => '99-99 33rd St',
'city' => 'Some City',
'state' => 'NY',
'postalCode' => '11101',
'businessClassification' => '9ed3f670-7d6f-11e3-b1ce-5404a6144203',
'businessType' => 'soleProprietorship',
'businessName' => 'Jane Corp',
'ein' => '00-0000000']);
?>
```
```ruby create_verified_customer.rb theme={"dark"}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
request_body = {
:firstName => 'Jane',
:lastName => 'Merchant',
:email => 'solePropBusiness@email.com',
:ipAddress => '143.156.7.8',
:type => 'business',
:dateOfBirth => '1980-01-31',
:ssn => '6789',
:address1 => '99-99 33rd St',
:city => 'Some City',
:state => 'NY',
:postalCode => '11101',
:businessClassification => '9ed3f670-7d6f-11e3-b1ce-5404a6144203',
:businessType => 'soleProprietorship',
:businessName => 'Jane Corp',
:ein => '00-0000000'
}
customer = app_token.post "customers", request_body
customer.response_headers[:location] # => "https://api-sandbox.dwolla.com/customers/62c3aa1b-3a1b-46d0-ae90-17304d60c3d5"
```
```python create_verified_customer.py theme={"dark"}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
request_body = {
'firstName': 'Jane',
'lastName': 'Merchant',
'email': 'solePropBusiness@email.com',
'ipAddress': '143.156.7.8',
'type': 'business',
'dateOfBirth': '1980-01-31',
'ssn': '6789',
'address1': '99-99 33rd St',
'city': 'Some City',
'state': 'NY',
'postalCode': '11101',
'businessClassification': '9ed3f670-7d6f-11e3-b1ce-5404a6144203',
'businessType': 'soleProprietorship',
'businessName': 'Jane Corp',
'ein': '00-0000000'
}
customer = app_token.post('customers', request_body)
customer.headers['location'] # => 'https://api-sandbox.dwolla.com/customers/62c3aa1b-3a1b-46d0-ae90-17304d60c3d5'
```
```javascript create_verified_customer.js theme={"dark"}
var requestBody = {
firstName: "Jane",
lastName: "Merchant",
email: "solePropBusiness@email.com",
ipAddress: "143.156.7.8",
type: "business",
dateOfBirth: "1980-01-31",
ssn: "6789",
address1: "99-99 33rd St",
city: "Some City",
state: "NY",
postalCode: "11101",
businessClassification: "9ed3f670-7d6f-11e3-b1ce-5404a6144203",
businessType: "soleProprietorship",
businessName: "Jane Corp",
ein: "00-0000000",
};
dwolla
.post("customers", requestBody)
.then((res) => res.headers.get("location")); // => 'https://api-sandbox.dwolla.com/customers/62c3aa1b-3a1b-46d0-ae90-17304d60c3d5'
```
When the customer is created, you'll receive the customer URL in the location header.
There are various reasons a Verified Customer will result in a status other than verified which you will want to account for after the Customer is created. Reference the Customer verification resource article for more information on handling verification statuses.
# Step 2 - Create unverified funding source
Next, we'll add Jane Merchant's bank or credit union account as an unverified funding source. Unverified funding sources can only receive funds, not send.
The example below shows sample bank information, but you will include actual bank name, routing, and account numbers after prompting your customer for this information within your application. Possible values for `bankAccountType` can be either "checking" or "savings". More detail is available in [API docs](/docs/api-reference/funding-sources/create-customer-funding-source).
```bash HTTP theme={"dark"}
POST https://api-sandbox.dwolla.com/customers/AB443D36-3757-44C1-A1B4-29727FB3111C/funding-sources
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNicvoWhDbQcVSKLRUpGjIdlPSEYyrHqrDDoRnQwE7Q
{
"routingNumber": "222222226",
"accountNumber": "123456789",
"bankAccountType": "checking",
"name": "Jane Merchant"
}
HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31
```
```ruby create_unverified_funding_source.rb theme={"dark"}
customer_url = 'https://api-sandbox.dwolla.com/customers/62c3aa1b-3a1b-46d0-ae90-17304d60c3d5'
request_body = {
routingNumber: '222222226',
accountNumber: '123456789',
bankAccountType: 'checking',
name: 'Jane Merchant'
}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
funding_source = app_token.post "#{customer_url}/funding-sources", request_body
funding_source.response_headers[:location] # => "https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31"
```
```javascript create_unverified_funding_source.js theme={"dark"}
var customerUrl =
"https://api-sandbox.dwolla.com/customers/62c3aa1b-3a1b-46d0-ae90-17304d60c3d5";
var requestBody = {
routingNumber: "222222226",
accountNumber: "123456789",
bankAccountType: "checking",
name: "Jane Merchant",
};
dwolla
.post(`${customerUrl}/funding-sources`, requestBody)
.then((res) => res.headers.get("location")); // => 'https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31'
```
```python create_unverified_funding_source.py theme={"dark"}
customer_url = 'https://api-sandbox.dwolla.com/customers/62c3aa1b-3a1b-46d0-ae90-17304d60c3d5'
request_body = {
'routingNumber': '222222226',
'accountNumber': '123456789',
'bankAccountType': 'checking',
'name': 'Jane Merchant'
}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
customer = app_token.post('%s/funding-sources' % customer_url, request_body)
customer.headers['location'] # => 'https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31'
```
```php create_unverified_funding_source.php theme={"dark"}
createCustomerFundingSource(array (
'routingNumber' => '222222226',
'accountNumber' => '123456789',
'bankAccountType' => 'checking',
'name' => 'Jane Merchant',
), $customer
);
print($new_fs); # => https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31
?>
```
The created funding source URL is returned in the Location header.
# Step 3 - Creating an Unverified Customer
Now that we've created a customer for Jane Merchant and associated a funding source, we'll do the same for Joe Buyer, but this time we'll create an `Unverified Customer`, and a verified funding source which is capable of sending money.
Provide the user's full name, email address, and IP address to create the Customer. More detail is available in [API docs](https://developers.dwolla.com/api-reference/customers).
Provide the IP address of the end user accessing your application as the ipAddress parameter. This enhances Dwolla's ability to detect fraud.
```bash HTTP theme={"dark"}
POST https://api-sandbox.dwolla.com/customers
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNicvoWhDbQcVSKLRUpGjIdlPSEYyrHqrDDoRnQwE7Q
{
"firstName": "Joe",
"lastName": "Buyer",
"email": "jbuyer@mail.net"
}
HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/customers/247B1BD8-F5A0-4B71-A898-F62F67B8AE1C
```
```ruby create_unverified_customer.rb theme={"dark"}
request_body = {
:firstName => 'Joe',
:lastName => 'Buyer',
:email => 'jbuyer@mail.net',
:ipAddress => '99.99.99.99'
}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
customer = app_token.post "customers", request_body
customer.response_headers[:location] # => "https://api-sandbox.dwolla.com/customers/247B1BD8-F5A0-4B71-A898-F62F67B8AE1C"
```
```javascript create_unverified_customer.js theme={"dark"}
var requestBody = {
firstName: "Joe",
lastName: "Buyer",
email: "jbuyer@mail.net",
ipAddress: "99.99.99.99",
};
dwolla
.post("customers", requestBody)
.then((res) => res.headers.get("location")); // => 'https://api-sandbox.dwolla.com/customers/247B1BD8-F5A0-4B71-A898-F62F67B8AE1C'
```
```python create_unverified_customer.py theme={"dark"}
request_body = {
'firstName': 'Joe',
'lastName': 'Buyer',
'email': 'jbuyer@mail.net',
'ipAddress': '99.99.99.99'
}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
customer = app_token.post('customers', request_body)
customer.headers['location'] # => 'https://api-sandbox.dwolla.com/customers/247B1BD8-F5A0-4B71-A898-F62F67B8AE1C'
```
```php create_unverified_customer.php theme={"dark"}
create([
'firstName' => 'Joe',
'lastName' => 'Buyer',
'email' => 'jbuyer@mail.net',
'ipAddress' => '99.99.99.99'
]);
print($new_customer); # => https://api-sandbox.dwolla.com/customers/247B1BD8-F5A0-4B71-A898-F62F67B8AE1C
?>
```
When the customer is created, you'll receive the customer URL in the location header.
# Step 4 - Attach a verified funding source
Next, you will create and attach a verified funding source to Joe Buyer, which will be done using Dwolla's Open Banking solution with Plaid, a leading Open Banking service provider that Dwolla partners with. This method will give Joe Buyer the ability to add and verify their bank account in a matter of seconds by authenticating using their online banking credentials.
Once Joe Buyer reaches the page in your application to add a bank account, you will use Open Banking with Plaid to authenticate their bank account. This involves initiating an Exchange Session with Dwolla, guiding the user through the verification process with their bank, and then using the Exchange details to create a funding source in Dwolla.
To integrate Open Banking with Plaid, we recommend checking out our [integration guide](/docs/open-banking/plaid). Additionally, if you would like to see a working example that verifies a bank using Open Banking with Plaid and attaches it as a verified funding source to a Dwolla Customer, please check out our [open-banking/plaid](https://github.com/Dwolla/integration-examples/tree/main/packages/open-banking/plaid) integration example on our GitHub profile.
Finally, once an instantly-verified funding source has been created via Open Banking with Plaid, Joe Buyer is now set up and ready to send money!
# Step 5 - Initiating a transfer
```bash HTTP theme={"dark"}
POST https://api-sandbox.dwolla.com/transfers
Accept: application/vnd.dwolla.v1.hal+json
Content-Type: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNicvoWhDbQcVSKLRUpGjIdlPSEYyrHqrDDoRnQwE7Q
{
"_links": {
"source": {
"href": "https://api-sandbox.dwolla.com/funding-sources/80275e83-1f9d-4bf7-8816-2ddcd5ffc197"
},
"destination": {
"href": "https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31"
}
},
"amount": {
"currency": "USD",
"value": "225.00"
}
}
HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388
```
```ruby initiate_transfer.rb theme={"dark"}
request_body = {
:_links => {
:source => {
:href => "https://api-sandbox.dwolla.com/funding-sources/80275e83-1f9d-4bf7-8816-2ddcd5ffc197"
},
:destination => {
:href => "https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31"
}
},
:amount => {
:currency => "USD",
:value => "225.00"
}
}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
# For Dwolla API applications, an app_token can be used for this endpoint. (https://developers.dwolla.com/docs/api-reference/tokens/create-an-application-access-token)
transfer = app_token.post "transfers", request_body
transfer.response_headers[:location] # => "https://api.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388"
```
```javascript initiate_transfer.js theme={"dark"}
var requestBody = {
_links: {
source: {
href: "https://api-sandbox.dwolla.com/funding-sources/80275e83-1f9d-4bf7-8816-2ddcd5ffc197",
},
destination: {
href: "https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31",
},
},
amount: {
currency: "USD",
value: "225.00",
},
};
// For Dwolla API applications, an dwolla can be used for this endpoint. (https://developers.dwolla.com/docs/api-reference/tokens/create-an-application-access-token)
dwolla
.post("transfers", requestBody)
.then((res) => res.headers.get("location")); // => 'https://api.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388'
```
```python initiate_transfer.py theme={"dark"}
request_body = {
'_links': {
'source': {
'href': 'https://api-sandbox.dwolla.com/funding-sources/80275e83-1f9d-4bf7-8816-2ddcd5ffc197'
},
'destination': {
'href': 'https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31'
}
},
'amount': {
'currency': 'USD',
'value': '225.00'
}
}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
# For Dwolla API applications, an app_token can be used for this endpoint. (https://developers.dwolla.com/docs/api-reference/tokens/create-an-application-access-token)
transfer = app_token.post('transfers', request_body)
transfer.headers['location'] # => 'https://api.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388'
```
```php initiate_transfer.php theme={"dark"}
array (
'source' =>
array (
'href' => 'https://api-sandbox.dwolla.com/funding-sources/80275e83-1f9d-4bf7-8816-2ddcd5ffc197',
),
'destination' =>
array (
'href' => 'https://api-sandbox.dwolla.com/funding-sources/375c6781-2a17-476c-84f7-db7d2f6ffb31',
),
),
'amount' =>
array (
'currency' => 'USD',
'value' => '225.00',
)
);
$transferApi = new DwollaSwagger\TransfersApi($apiClient);
$myAccount = $transferApi->create($transfer_request);
print($xfer); # => https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388
?>
```
## Retrieve the status of your transfer
You can check the status of the newly created transfer by retrieving the transfer by its URL.
##### Request and response
```bash HTTP theme={"dark"}
GET https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer pBA9fVDBEyYZCEsLf/wKehyh1RTpzjUj5KzIRfDi0wKTii7DqY
...
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/transfers/D76265CD-0951-E511-80DA-0AA34A9B2388"
},
"source": {
"href": "https://api-sandbox.dwolla.com/customers/AB443D36-3757-44C1-A1B4-29727FB3111C"
},
"destination": {
"href": "https://api-sandbox.dwolla.com/customers/C7F300C0-F1EF-4151-9BBE-005005AC3747"
}
},
"id": "D76265CD-0951-E511-80DA-0AA34A9B2388",
"status": "pending",
"amount": {
"value": "225.00",
"currency": "USD"
},
"created": "2015-09-02T00:30:25.580Z"
}
```
```ruby get_transfer_status.rb theme={"dark"}
transfer_url = 'https://api-sandbox.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388'
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
# For Dwolla API applications, an app_token can be used for this endpoint. (https://developers.dwolla.com/api-reference/authorization/application-authorization)
transfer = app_token.get transfer_url
transfer.status # => "pending"
```
```php get_transfer_status.php theme={"dark"}
byId($transferUrl);
$transfer->status; # => "pending"
?>
```
```python get_transfer_status.py theme={"dark"}
transfer_url = 'https://api.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388'
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
# For Dwolla API applications, an app_token can be used for this endpoint. (https://developers.dwolla.com/api-reference/authorization/application-authorization)
transfer = app_token.get(transfer_url)
transfer.body['status'] # => 'pending'
```
```javascript get_transfer_status.js theme={"dark"}
var transferUrl =
"https://api.dwolla.com/transfers/d76265cd-0951-e511-80da-0aa34a9b2388";
// For Dwolla API applications, an dwolla can be used for this endpoint. (https://developers.dwolla.com/api-reference/authorization/application-authorization)
dwolla.get(transferUrl).then((res) => res.body.status); // => 'pending'
```
That's it! You've successfully transferred money from Joe Buyer to Jane Merchant. Please continue to the [Webhooks guide](/docs/working-with-webhooks) for information on implementing notifications for your customers about the transfer.
# Transfer Money Me-to-Me
Source: https://developers.dwolla.com/docs/transfer-money-me-to-me
Move funds between two different bank accounts belonging to a single Verified Customer, e.g. for savings applications that move funds between a Customer's checking and savings accounts.
## Overview
This guide is designed to get you up and running quickly through creating a bank to bank transfer between a verified Customer's two bank accounts. In this guide we'll cover the basics of integrating this payment flow by walking through the steps needed to onboard your end user as a verified Customer and create the bank to bank transfer.
In this quickstart guide, you'll learn the key concepts involved with sending money between a Customer's bank accounts.
Select the appropriate Customer type and create a verified Customer in your application.
Link both a source and a destination bank account to the Customer's profile for account-to-account transfers.
Retrieve the list of all funding sources associated with the Customer.
Initiate a transfer from the Customer's checking account to their savings account using the Dwolla API.
## Before you begin
We encourage you to create a Sandbox account, if you haven't already. This will allow you to follow along with the steps outlined in this guide. Check out our [Sandbox guide](/docs/testing) to learn more.
After creating a sandbox account, you'll obtain your API Key and Secret, which are used to obtain an OAuth access token. An access token is required in order to authenticate against the Dwolla API. If you haven't already, run through the [Quickstart](/docs/quickstart) to get your first token, or see the [Authentication guide](/docs/api-reference/api-fundamentals/making-requests-and-authentication) for OAuth details.
Lastly, in this sandbox walkthrough, we recommend having an active webhook subscription. This will help notify your application of various events that occur within Dwolla. [Check out our guide to learn more](/docs/working-with-webhooks).
**Let's get started!**
# Step 1 - Creating your Customer
#### Choose the Customer Type for Your Funds Flow
Before your end user can send or receive funds to their connected bank account, they must be created as a Customer via the Dwolla API. With this funds flow, however, the only eligible Customer types are:
* Verified Personal Customers
* Verified Business Customers
To learn more on the differences between personal and business verified Customers and the capabilities of each, [check out our developer resource article.](/docs/customer-types)
Verified Customers must go through the identity verification process and have
a verified status in order to be eligible to transact. In order
to verify the identity of the individual or business creating a Dwolla
Customer account, you will need to pass information including, but not limited
to, social security number (SSN), address, date of birth, and/or Employer
Identification Number (EIN).
### Create the Customer
While both the Personal and Business verified Customer types are valid in this funds flow, we will be creating a Personal verified Customer in this guide.
##### Request Parameters - Personal Verified Customer
| Parameter | Required | Type | Description |
| ----------- | -------- | ------ | ------------------------------------------------------------------------------------ |
| firstName | yes | string | Individual's legal first name. |
| lastName | yes | string | Individual's legal last name. |
| email | yes | string | Customer's email address. |
| type | yes | string | Type of identity verified Customer. Value of `personal` for individual. |
| address1 | yes | string | Street number, street name of individual's physical address. |
| address2 | no | string | Apartment, floor, suite, bldg # of individual's physical address. |
| city | yes | string | City of individual's physical address. |
| state | yes | string | Two-letter US state or territory abbreviation code of individual's physical address. |
| postalCode | yes | string | Customer's US five-digit ZIP or ZIP + 4 code. |
| dateOfBirth | yes | string | Customer's date of birth. Must be 18 years of age with format of `YYYY-MM-DD`. |
| ssn | yes | string | Last four-digits of individual's social security number. |
##### Request and response
```bash HTTP theme={"dark"}
POST https://api-sandbox.dwolla.com/customers
Content-Type: application/vnd.dwolla.v1.hal+json
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer pBA9fVDBEyYZCEsLf/wKehyh1RTpzjUj5KzIRfDi0wKTii7DqY
{
"firstName": "John",
"lastName": "Doe",
"email": "johndoe@nomail.net",
"ipAddress": "10.10.10.10",
"type": "personal",
"address1": "99-99 33rd St",
"city": "Some City",
"state": "NY",
"postalCode": "11101",
"dateOfBirth": "1970-01-01",
"ssn": "1234"
}
HTTP/1.1 201 Created
Location: https://api.dwolla.com/customers/FC451A7A-AE30-4404-AB95-E3553FCD733F
```
```php create_personal_verified_customer.php theme={"dark"}
create([
'firstName' => 'John',
'lastName' => 'Doe',
'email' => 'jdoe@nomail.net',
'type' => 'personal',
'address1' => '99-99 33rd St',
'city' => 'Some City',
'state' => 'NY',
'postalCode' => '11101',
'dateOfBirth' => '1970-01-01',
# For the first attempt, only the
# last 4 digits of SSN required
# If the entire SSN is provided,
# it will still be accepted
'ssn' => '1234'
]);
$customer; # => "https://api-sandbox.dwolla.com/customers/AB443D36-3757-44C1-A1B4-29727FB3111C"
?>
```
```ruby create_personal_verified_customer.rb theme={"dark"}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby
request_body = {
:firstName => 'John',
:lastName => 'Doe',
:email => 'jdoe@nomail.net',
:type => 'personal',
:address1 => '99-99 33rd St',
:city => 'Some City',
:state => 'NY',
:postalCode => '11101',
:dateOfBirth => '1970-01-01',
# For the first attempt, only the
# last 4 digits of SSN required
# If the entire SSN is provided,
# it will still be accepted
:ssn => '1234'
}
customer = app_token.post "customers", request_body
customer.response_headers[:location] # => "https://api-sandbox.dwolla.com/customers/AB443D36-3757-44C1-A1B4-29727FB3111C"
```
```python create_personal_verified_customer.py theme={"dark"}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python
request_body = {
'firstName': 'John',
'lastName': 'Doe',
'email': 'jdoe@nomail.net',
'type': 'personal',
'address1': '99-99 33rd St',
'city': 'Some City',
'state': 'NY',
'postalCode': '11101',
'dateOfBirth': '1970-01-01',
# For the first attempt, only the
# last 4 digits of SSN required
# If the entire SSN is provided,
# it will still be accepted
'ssn': '1234'
}
customer = app_token.post('customers', request_body)
customer.headers['location'] # => 'https://api-sandbox.dwolla.com/customers/AB443D36-3757-44C1-A1B4-29727FB3111C'
```
```javascript create_personal_verified_customer.js theme={"dark"}
var requestBody = {
firstName: "John",
lastName: "Doe",
email: "jdoe@nomail.net",
type: "personal",
address1: "99-99 33rd St",
city: "Some City",
state: "NY",
postalCode: "11101",
dateOfBirth: "1970-01-01",
// For the first attempt, only the
// last 4 digits of SSN required
// If the entire SSN is provided,
// it will still be accepted
ssn: "1234",
};
dwolla
.post("customers", requestBody)
.then((res) => res.headers.get("location")); // => 'https://api-sandbox.dwolla.com/customers/FC451A7A-AE30-4404-AB95-E3553FCD733F'
```
When the Customer is successfully created on your application, you will receive a `201` HTTP response with an empty response body. You can reference the Location header to retrieve a link that represents the created Customer resource. We recommend storing the full URL for future use, as it will be needed for actions such as attaching a bank or correlating webhooks that are triggered for the user in the Dwolla system.
Providing the IP address of the end user accessing your application as the
ipAddress parameter. This enhances fraud detection and tracking.
### Handle Webhooks
If you have an active webhook subscription, you will receive the `customer_created` and `customer_verified` webhook immediately after the resource has been created.
### Additional expected behavior
#### Customer Statuses
Not all Customers will have a `verified` status upon initial Customer creation. In production, you may run into an instance where more information is needed from your end user in order for Dwolla to fully verify their identity. Other statuses your Customer may be placed in include, `retry`, `document`, `deactivated`, or `suspended`. For more information on these statuses, refer to our developer resource article.
#### Balance Funding Source
On successful Customer verification, Dwolla will also create a [Balance Funding Source](/docs/balance-funding-source) for this Customer.
There are two types of Funding Sources available within the Dwolla Platform
which include a bank or a balance. A bank account is commonly used as the
source or destination for ACH transfers. A balance is a Funding
Source that can be utilized like a "wallet" for holding a stored value of
funds. The Dwolla balance is made available for Customers that have fully
verified their identity within Dwolla.
# Step 2 - Adding Funding Sources
Within Dwolla, the sending party must always verify their bank account in order to be eligible to create a transfer.
#### Bank Addition and Verification Methods
There are multiple ways of adding a bank to a Customer with the Dwolla API. A simplified table below outlines the similarities and differences of each method.
| Bank Addition Method | Will the bank be verified? | Required Information |
| ---------------------------------------------------------- | ----------------------------- | ------------------------------- |
| API - Account & Routing Number | Optional - With Microdeposits | Bank Account and Routing Number |
| [Dwolla + Open Banking](/docs/open-banking#overview) | Yes | Online banking credentials |
| [Drop-in components](/docs/drop-in-components) | Optional - With Microdeposits | Bank Account and Routing Number |
| [Dwolla + Secure Exchange solution](/docs/secure-exchange) | Yes | Online banking credentials |
| Other Approved Third-party Provider | Yes | Variable |
For more information on securely submitting a user's bank details directly to
Dwolla from the client-side of your application, reference our
Drop-in components
.
### Add a Bank to a Verified Personal Customer
In this step, we will create and attach a verified funding source to your Customer using Dwolla's Open Banking solution with Plaid, a leading Open Banking service provider that Dwolla partners with. This method will give your Customers the ability to add and verify their bank account in a matter of seconds by authenticating using their online banking credentials.
Once your Customer reaches the page in your application to add a bank account, you will use Open Banking with Plaid to authenticate the user's bank account. This involves initiating an Exchange Session with Dwolla, guiding the user through the verification process with their bank, and then using the Exchange details to create a funding source in Dwolla.
To integrate Open Banking with Plaid, we recommend checking out our [integration guide](/docs/open-banking/plaid). Additionally, if you would like to see a working example that verifies a bank using Open Banking with Plaid and attaches it as a verified funding source to a Dwolla Customer, please check out our [open-banking/plaid](https://github.com/Dwolla/integration-examples/tree/main/packages/open-banking/plaid) integration example on our GitHub profile.
### Handle Webhooks
If you have an active webhook subscription, you should receive both the `customer_funding_source_added` and `customer_funding_source_verified` webhooks immediately following the request to Dwolla to add a funding source using Open Banking.
### Add a Savings Account
Once you have implemented Dwolla's Open Banking solution with Plaid, and you have attached a customer's `checking` account, you will want to repeat the same steps outlined above to add the customer's `savings` account as a funding source as well.
# Step 3 - Retrieve Funding Sources
In order to find your Customer's available bank and balance funding sources, you will need to first retrieve the funding sources from your Customer, via the API.
##### Request and response
```bash HTTP [expandable] theme={"dark"}
GET https://api-sandbox.dwolla.com/customers/5b29279d-6359-4c87-a318-e09095532733/funding-sources
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer pBA9fVDBEyYZCEsLf/wKehyh1RTpzjUj5KzIRfDi0wKTii7DqY
...
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/customers/5b29279d-6359-4c87-a318-e09095532733/funding-sources"
},
"customer": {
"href": "https://api-sandbox.dwolla.com/customers/5b29279d-6359-4c87-a318-e09095532733"
}
},
"_embedded": {
"funding-sources": [
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/funding-sources/ab9cd5de-9435-47af-96fb-8d2fa5db51e8"
},
"customer": {
"href": "https://api-sandbox.dwolla.com/customers/5b29279d-6359-4c87-a318-e09095532733"
},
"with-available-balance": {
"href": "https://api-sandbox.dwolla.com/funding-sources/ab9cd5de-9435-47af-96fb-8d2fa5db51e8"
}
},
"id": "ab9cd5de-9435-47af-96fb-8d2fa5db51e8",
"status": "verified",
"type": "balance",
"name": "Balance",
"created": "2015-10-02T21:00:28.153Z",
"removed": false,
"channels": []
},
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/funding-sources/98c209d3-02d6-4bee-bc0f-61e18acf0e33"
},
"customer": {
"href": "https://api-sandbox.dwolla.com/customers/5b29279d-6359-4c87-a318-e09095532733"
}
},
"id": "98c209d3-02d6-4bee-bc0f-61e18acf0e33",
"status": "verified",
"type": "bank",
"bankAccountType": "checking",
"name": "Jane Doe's Checking",
"created": "2015-10-02T22:03:45.537Z",
"removed": false,
"channels": [
"ach"
],
"fingerprint": "4cf31392f678cb26c62b75096e1a09d4465a801798b3d5c3729de44a4f54c794"
},
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/funding-sources/32eb2e53-d1e8-4b4d-bfc7-9ae7c553969d"
},
"customer": {
"href": "https://api-sandbox.dwolla.com/customers/5b29279d-6359-4c87-a318-e09095532733"
}
},
"id": "98c209d3-02d6-4bee-bc0f-61e18acf0e33",
"status": "verified",
"type": "bank",
"bankAccountType": "savings",
"name": "Jane Doe's Savings",
"created": "2015-10-02T22:03:45.537Z",
"removed": false,
"channels": [
"ach"
]
}
]
}
}
```
```php get_funding_sources.php theme={"dark"}
getCustomerFundingSources($customerUrl);
$fundingSources->_embedded->{'funding-sources'}[0]->name; # => "Jane Doe's Checking"
?>
```
```ruby get_funding_sources.rb theme={"dark"}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby
customer_url = 'https://api-sandbox.dwolla.com/customers/5b29279d-6359-4c87-a318-e09095532733'
funding_sources = app_token.get "#{customer_url}/funding-sources"
funding_sources._embedded['funding-sources'][0].name # => "Jane Doe's Checking"
```
```python get_funding_sources.py theme={"dark"}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python
customer_url = 'https://api-sandbox.dwolla.com/customers/5b29279d-6359-4c87-a318-e09095532733'
funding_sources = app_token.get('%s/funding-sources' % customer_url)
funding_sources.body['_embedded']['funding-sources'][0]['name'] # => 'Jane Doe's Checking'
```
```javascript get_funding_sources.js theme={"dark"}
var customerUrl =
"https://api-sandbox.dwolla.com/customers/5b29279d-6359-4c87-a318-e09095532733";
dwolla
.get(`${customerUrl}/funding-sources`)
.then((res) => res.body._embedded["funding-sources"][0].name); // => 'Jane Doe's Checking'
```
When the funding sources are successfully retrieved, you will receive a `200` HTTP response with the details of the funding sources. After retrieving the funding sources, we recommend storing the full URL for future use as it will be referenced when creating the transfer to this user's bank account.
# Step 4 - Initiating a Transfer
Sending funds from the Customer's checking account to their savings account.
#### Identify Source and Destination For Transfer
The first step is to determine where the funds are being sourced from and where the funds are going to.
* `Source` - Your Customer's Checking Bank Funding Source
* `Destination` - Your Customer's Savings Bank Funding Source
Since you are utilizing a `me-to-me` funds flow, you will need to know that there are two parts to a transfer,
* Source funding source to Balance funding source
* Balance funding source to Destination funding source
### Initiate a Transfer
To initiate a transfer, we will need to specify the source and destination funding source URLs in the `_links` parameter.
##### Request Parameters
| Parameter | Required | Type | Description |
| --------- | -------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| \_links | yes | object | A \_links JSON object describing the desired source and destination of a transfer. [Reference the Source and Destination object](/docs/api-reference/transfers) to learn more about possible values for source and destination. |
| amount | yes | object | An amount JSON object. [Reference the amount JSON object](/docs/api-reference/transfers) to learn more. |
Within a transfer request, Dwolla supports additional optional parameters.
These can range from clearing to specify the processing timing
for the transfer, or correlationId to help correlate transfers
from end-to-end. The object facilitator-fee isn't supported for
this funds flow. For more information on all available transfer request
parameters, check out our API reference documentation.
##### Request and response
```bash HTTP theme={"dark"}
POST https://api-sandbox.dwolla.com/transfers
Accept: application/vnd.dwolla.v1.hal+json
Content-Type: application/vnd.dwolla.v1.hal+json
Authorization: Bearer pBA9fVDBEyYZCEsLf/wKehyh1RTpzjUj5KzIRfDi0wKTii7DqY
Idempotency-Key: 19051a62-3403-11e6-ac61-9e71128cae77
{
"_links": {
"source": {
"href": "https://api-sandbox.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4"
},
"destination": {
"href": "https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e"
}
},
"amount": {
"currency": "USD",
"value": "10.00"
},
}
...
HTTP/1.1 201 Created
Location: https://api-sandbox.dwolla.com/transfers/81643da1-b1b3-e911-811b-f08aa77f5aa3
```
```php initiate_transfer.php theme={"dark"}
array (
'source' =>
array (
'href' => 'https://api-sandbox.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4',
),
'destination' =>
array (
'href' => 'https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e',
),
),
'amount' =>
array (
'currency' => 'USD',
'value' => '225.00',
)
);
$transferApi = new DwollaSwagger\TransfersApi($apiClient);
$transfer = $transferApi->create($transfer_request);
print($transfer); # => https://api-sandbox.dwolla.com/transfers/81643da1-b1b3-e911-811b-f08aa77f5aa3
?>
```
```ruby initiate_transfer.rb theme={"dark"}
transfer_request = {
:_links => {
:source => {
:href => "https://api-sandbox.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4"
},
:destination => {
:href => "https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e"
}
},
:amount => {
:currency => "USD",
:value => "225.00"
}
}
# Using DwollaV2 - https://github.com/Dwolla/dwolla-v2-ruby (Recommended)
transfer = app_token.post "transfers", transfer_request
transfer.response_headers[:location] # => "https://api-sandbox.dwolla.com/transfers/81643da1-b1b3-e911-811b-f08aa77f5aa3"
```
```python initiate_transfer.py theme={"dark"}
transfer_request = {
'_links': {
'source': {
'href': 'https://api-sandbox.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4'
},
'destination': {
'href': 'https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e'
}
},
'amount': {
'currency': 'USD',
'value': '225.00'
}
}
# Using dwollav2 - https://github.com/Dwolla/dwolla-v2-python (Recommended)
transfer = app_token.post('transfers', transfer_request)
transfer.headers['location'] # => 'https://api-sandbox.dwolla.com/transfers/81643da1-b1b3-e911-811b-f08aa77f5aa3'
```
```javascript initiate_transfer.js theme={"dark"}
var transferRequest = {
_links: {
source: {
href: "https://api-sandbox.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4",
},
destination: {
href: "https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e",
},
},
amount: {
currency: "USD",
value: "225.00",
},
};
dwolla.post("transfers", transferRequest).then(function (res) {
res.headers.get("location"); // => 'https://api-sandbox.dwolla.com/transfers/81643da1-b1b3-e911-811b-f08aa77f5aa3'
});
```
### Handle Webhooks
If you have an active webhook subscription (required in production & optional in Sandbox), you will receive the `customer_bank_transfer_created` webhook immediately after the transfer resource has been created. This denotes that the first part of the transfer has been initiated from the checking bank funding source to the Balance funding source.
### Simulate Bank Transfer Processing
To simulate bank transfer processing in the Dwolla Sandbox environment, navigate to the Sandbox Dashboard. From here, you will want to click the "Process Bank Transfers" button on the top of the screen. Your Sandbox transfer will be moved out of a pending status and moved to a processed status.
**Production bank transfer processing timing**
While pending bank transfers can be processed at any time in the Sandbox, behavior will vary in production depending on if your application has access to expedited transfers versus standard ACH transfer times. Refer to our [developer resource article](/docs/transfer-processing-times) to learn more on transfer timing in production.
### Handle Webhooks
If you have an active webhook subscription (required in production & optional in Sandbox), you will receive the `customer_bank_transfer_completed` webhook when the status of the first part of the transfer has changed from `pending` to `processed`. The second part of the transfer is then automatically initiated from the Balance funding source to the savings bank funding-source which triggers another `customer_bank_transfer_created` webhook. Repeat [simulate bank transfer processing](#simulate-bank-transfer-processing) to simulate bank transfer processing again. Once the transfer has cleared into the final destination bank funding source, you will receive another `customer_bank_transfer_completed` webhook denoting that the transfer is now complete.
### Verify Status of Transfer
Since ACH transfers in production can take a few days to complete, webhooks are an efficient way to notify you of when a transfer's status has been updated from `pending` to `processed` to a destination funding source. However, if you want to verify the status of a transfer at any given point in time, you can make a call to the API to retrieve the transfer by its unique id.
##### Example response - Transfer 1
```json theme={"dark"}
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/transfers/81643da1-b1b3-e911-811b-f08aa77f5aa3",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
},
"source": {
"href": "https://api-sandbox.dwolla.com/funding-sources/707177c3-bf15-4e7e-b37c-55c3898d9bf4",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "funding-source"
},
"destination": {
"href": "https://api-sandbox.dwolla.com/customers/cf3f1ad4-fc48-45d3-8aff-0ef5577b8a17",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "customer"
},
"funded-transfer": {
"href": "https://api-sandbox.dwolla.com/transfers/a00ff82d-73b4-e911-811b-f08aa77f5aa3",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
}
},
"id": "81643da1-b1b3-e911-811b-f08aa77f5aa3",
"created": "2019-08-01T15:32:05.620Z",
"status": "processed",
"amount": {
"value": "225.00",
"currency": "USD"
},
"individualAchId": "IQ8M922R"
}
```
##### Example response - Transfer 2
```json theme={"dark"}
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/transfers/a00ff82d-73b4-e911-811b-f08aa77f5aa3",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
},
"source": {
"href": "https://api-sandbox.dwolla.com/customers/cf3f1ad4-fc48-45d3-8aff-0ef5577b8a17",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "customer"
},
"destination": {
"href": "https://api-sandbox.dwolla.com/funding-sources/3152c22b-3d72-442d-a83b-e575df3a043e",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "funding-source"
},
"funding-transfer": {
"href": "https://api-sandbox.dwolla.com/transfers/81643da1-b1b3-e911-811b-f08aa77f5aa3",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
}
},
"id": "a00ff82d-73b4-e911-811b-f08aa77f5aa3",
"created": "2019-08-01T15:44:06.290Z",
"status": "processed",
"amount": {
"value": "225.00",
"currency": "USD"
},
"individualAchId": "IQKBPJAY"
}
```
# Transfer Processing Times
Source: https://developers.dwolla.com/docs/transfer-processing-times
Learn more about transfer clearing times, including `standard` ,`expedited` and `instant` clearing.
## Overview
The transfer processing time denotes the duration required for a bank transfer to complete. Understanding transfer processing times helps ensure seamless user experience, workflow optimization, and business operations. By managing expectations and designing efficient processes, you can enhance your customer experience and drive business success.
## Bank-to-Bank Transfers
Bank-to-bank transfers at Dwolla involve two steps:
* Payment In (Debit) - Funds are pulled from the sender's bank account (source) into the Dwolla Network.
* Payment Out (Credit) - Funds are pushed into the recipient's bank account (destination) from the Dwolla Network.
The transfer processing time depends on the transfer speed chosen for both the debit and credit sides of the transfer.
Reference our Transfer Lifecycle, which explains bank-to-bank transfers in full detail.
## Understanding Transfer Processing Times
## Standard ACH Transfers
Standard ACH is the default processing option for bank transfers, requiring no additional setup or configuration. Any transfer created will automatically follow the standard clearing time. It applies to both ACH payment in (debit) and ACH payment out (credit) transfers.
#### Key Points to Note for Standard ACH Transfers:
* **Export deadline**:
* 4 p.m. CT
* **Transfer processing time**:
* Debit transfers: 3-4 business days
* Credit transfers: 1-2 business days
* **Processing schedule**: Business days only (unavailable on weekends and banking holidays)
### Standard ACH Debit (Payment In)
A Standard ACH debit from a bank to the Dwolla Network processes within 3-4 business days. This extended processing time accounts for ACH returns and processing delays, and checks on [common return codes](https://www.dwolla.com/resources/ach-return-codes/) like insufficient funds and invalid bank accounts.
##### Standard ACH Debit Transfer Schedule
All times in Central Time (CT) (\*approximate deadlines)
| Transfer created between | Transfer exported | Transfer processed |
| ----------------------------------- | ----------------- | ------------------ |
| Friday 4 p.m. to Monday 4 p.m. | Monday 4 p.m. | Thursday morning |
| Monday 4 p.m. to Tuesday 4 p.m. | Tuesday 4 p.m. | Friday morning |
| Tuesday 4 p.m. to Wednesday 4 p.m. | Wednesday 4 p.m. | Monday morning |
| Wednesday 4 p.m. to Thursday 4 p.m. | Thursday 4 p.m. | Tuesday morning |
| Thursday 4 p.m. to Friday 4 p.m. | Friday 4 p.m. | Wednesday morning |
*\*Dwolla cannot guarantee an exact deadline for transmission of ACH files. Please be mindful that these are target cut-off times for transfer processing.*
### Standard ACH Credit (Payment Out)
A Standard ACH credit from the Dwolla Network to a bank processes within 1-2 business days.
##### Standard ACH Credit Transfer Schedule
All times in Central Time (CT) (\*approximate deadlines)
| Transfer created between | Transfer exported | Transfer processed |
| ----------------------------------- | ----------------- | ------------------ |
| Friday 4 p.m. to Monday 4 p.m. | Monday 4 p.m. | Tuesday morning |
| Monday 4 p.m. to Tuesday 4 p.m. | Tuesday 4 p.m. | Wednesday morning |
| Tuesday 4 p.m. to Wednesday 4 p.m. | Wednesday 4 p.m. | Thursday morning |
| Wednesday 4 p.m. to Thursday 4 p.m. | Thursday 4 p.m. | Friday morning |
| Thursday 4 p.m. to Friday 4 p.m. | Friday 4 p.m. | Monday morning |
*\*Dwolla cannot guarantee an exact deadline for transmission of ACH files. Please be mindful that these are target cut-off times for transfer processing.*
## Next Day ACH Transfers
Dwolla offers expedited clearing into the Dwolla Network from a bank account in the form of Next Day ACH. Funds are made available in the Dwolla Network 1-2 business days after a Next Day ACH debit transfer is created. Next Day ACH transfers can be enabled on an account-level basis for you and users that send funds on your application.
Once obtaining approval from us, you will gain capability for Next Day ACH transfers into the Dwolla Network. Once activated, Next Day ACH is the default processing speed for all ACH debits unless you specify otherwise using the clearing object. Refer to the example clearing object below to slow down the processing time from Next Day to Standard.
Example `clearing` Object (to slow down Next Day ACH):
```raw theme={"dark"}
{
"clearing": {
"source": "standard"
}
}
```
#### Key Points to Note for Next Day ACH Transfers:
* **Available for**: Debit transfers only
* **Export deadline**:
* 4 p.m. CT
* **Transfer processing time**:
* Debit transfers: 1-2 business days
* **Processing schedule**: Business days only (unavailable on weekends and banking holidays)
##### Next Day ACH Debit Transfer Schedule
All times in Central Time (CT) (\*approximate deadlines)
| Transfer created between | Transfer exported | Transfer processed |
| ----------------------------------- | ----------------- | ------------------ |
| Friday 4 p.m. to Monday 4 p.m. | Monday 4 p.m. | Tuesday morning |
| Monday 4 p.m. to Tuesday 4 p.m. | Tuesday 4 p.m. | Wednesday morning |
| Tuesday 4 p.m. to Wednesday 4 p.m. | Wednesday 4 p.m. | Thursday morning |
| Wednesday 4 p.m. to Thursday 4 p.m. | Thursday 4 p.m. | Friday morning |
| Thursday 4 p.m. to Friday 4 p.m. | Friday 4 p.m. | Monday morning |
*\*Dwolla cannot guarantee an exact deadline for transmission of ACH files. Please be mindful that these are target cut-off times for transfer processing.*
## Same Day ACH Transfers
We can enable [Same Day ACH](/docs/same-day-ach) transfers on an account-level basis for transfers within your application. With Same Day ACH, you can apply it to the ACH debit, the ACH credit, or both. After receiving approval from Dwolla to enable Same Day ACH for your account, you can create a Same Day debit and/or credit on a per-transfer basis by specifying it in the clearing object of an API transfer request.
Transfer processing is expedited with Same Day ACH, but checks for common return codes may experience delays, which could increase the risk of losses incurred from return codes.
Reference our Concept article, which explains Same Day debits and credits in full detail.
#### Key Points to Note for Same Day ACH Transfers
* **Available for**: Debit and Credit transfers
* \*\*Export deadlines:
* 9 a.m. CT
* 1 p.m. CT
* 3 p.m. CT
* **Processing Time**: Once a Same Day ACH transfer is submitted before the deadline, it typically takes 0-1 business day to settle, regardless of whether it's a debit or credit.
**Important Note**: The [\$1 million transaction limit](https://www.dwolla.com/updates/access-faster-payments-with-dwolla-same-day-ach/) is enforced by Nacha, the governing body for ACH transfers.
### Same Day ACH Debit (Payment In)
A Same Day ACH debit from the Dwolla Network to a bank processes within 0-1 business days. To initiate a debit transfer with Same Day ACH processing, you can include the following clearing object in your API request:
Example `clearing` Object:
```raw theme={"dark"}
{
"clearing": {
"source": "next-available"
}
}
```
The "next-available" value indicates that the transfer will be exported in the next available Same Day ACH export window. Refer to the table below for a more detailed schedule of Same Day debit transfers.
##### Same Day ACH Debit Transfer Schedule
All times in Central Time (CT) (\*approximate deadlines)
| Transfer created between | Transfer exported | Transfer processed |
| ------------------------ | ----------------- | ------------------ |
| 3 p.m. to 9 a.m. | 9 a.m. | 11:30 a.m. |
| 9 a.m. to 1 p.m. | 1 p.m. | 5 p.m. |
| 1 p.m. to 3 p.m. | 3 p.m. | 5 p.m. |
*\*Dwolla cannot guarantee an exact deadline for transmission of ACH files. Please be mindful that these are target cut-off times for transfer processing.*
### Same Day ACH Credit (Payment Out)
A Same Day ACH credit allows for expedited processing of funds from the Dwolla Network to a bank account within 0-1 business days. To initiate a credit transfer with Same Day ACH processing, you can include the following clearing object in your API request:
Example `clearing` Object:
```raw theme={"dark"}
{
"clearing": {
"destination": "next-available"
}
}
```
The "next-available" value indicates that the transfer will be exported in the next available Same Day ACH export window. Refer to the table below for a more detailed schedule of Same Day credit transfers.
##### Same Day ACH Credit Transfer Schedule
All times in Central Time (CT) (\*approximate deadlines)
| Transfer created between | Transfer exported | Transfer processed |
| ------------------------ | ----------------- | ------------------ |
| 3 p.m. to 9 a.m. | 9 a.m. | 11:30 a.m. |
| 9 a.m. to 1 p.m. | 1 p.m. | 5 p.m. |
| 1 p.m. to 3 p.m. | 3 p.m. | 5 p.m. |
*\*Dwolla cannot guarantee an exact deadline for transmission of ACH files. Please be mindful that these are target cut-off times for transfer processing.*
## Instant Payments (RTP and FedNow)
[Instant Payments](/docs/instant-payments) is a payment method that allows for near-instantaneous transfer processing through two US-based payment networks:
* **RTP® Network** - Operated by The Clearing House (TCH)
* **FedNow® Service** - Operated by the Federal Reserve Bank (FRB)
When you initiate an Instant Payment, Dwolla routes the transfer to the appropriate network based on availability and configuration. The specific network used (RTP or FedNow) is indicated in the transfer response through either an `rtpDetails` object or a `fedNowDetails` object.
#### Key Points to Note for Instant Payment Transfers
* **Available for**: Credit transfers only
* **Export deadline**: None
* **Transfer processing time**: Funds available within minutes
* **Processing schedule**: 24/7/365 (business days, weekends and banking holidays)
To initiate a credit transfer with Instant Payment processing, you can include the following processingChannel object in your API request:
Example `processingChannel` Object:
```json theme={"dark"}
"processingChannel": {
"destination": "instant"
}
```
For backward compatibility you can also use `real-time-payments` as an alternative value for `processingChannel.destination`. Both values are functionally equivalent and will route to either RTP or FedNow based on availability. However, we recommend using `instant`.
## Transfer Processing Timeline Scenarios
This table outlines the expected processing times for different transfer scenarios:
| Bank Account to Dwolla Network Clearing | Dwolla Network to Bank Account Clearing | Time to Dwolla Network (Debit) | Time to Destination Bank Account (Credit) | Total time to processed |
| --------------------------------------- | --------------------------------------- | ------------------------------ | ----------------------------------------- | ----------------------- |
| Standard | Standard | 3-4 business days | 1-2 business days | 4-6 business days |
| Standard | Same Day | 3-4 business days | 0-1 business days | 3-5 business days |
| Standard | Instant Payments | 3-4 business days | Instant | 3-4 business days |
| Next Day | Standard | 1-2 business days | 1-2 business days | 2-4 business days |
| Next Day | Same Day | 1-2 business days | 0-1 business days | 1-3 business days |
| Next Day | Instant Payments | 1-2 business days | Instant | 1-2 business days |
| Same Day | Standard | 0-1 business days | 1-2 business days | 1-3 business days |
| Same Day | Same Day | 0-1 business days | 0-1 business days | 1-2 business days |
| Same Day | Instant Payments | 0-1 business days | Instant | 0-1 business days |
## Best Practices and Considerations
**Canceling a transfer:** Transfers can be canceled up until they are exported out of the Dwolla Network. To help you determine if a transfer is eligible for cancellation, Dwolla will return a cancel link on the transfer resource.
**Export cut-off:** Please note that while there's a cut-off time for transfer processing, it's not a strict deadline. In some cases, the export out of the Dwolla Network may experience delays, causing transfers created slightly after the cut-off time to be included in the same batch. As a best practice, we recommend using the [cancel link](https://developers.dwolla.com/docs/balance/api-reference/transfers#transfer-links) returned on the transfer resource to determine if a transfer is cancellable instead of relying solely on the export timing.
**ACH return:** Next Day ACH and Same Day ACH expedites the transfer processing time; however, checks for common return codes might experience delays which may increase the risk of losses incurred from return codes.
# Virtual Account Numbers
Source: https://developers.dwolla.com/docs/virtual-account-numbers
A Virtual Account Number (VAN) provides a dedicated routing path for external transactions to flow into and out of a Dwolla balance. Each VAN generates unique account and routing numbers that enable you to credit or debit a Dwolla balance through ACH transfers.
# Overview
Virtual accounts are virtual subledgers within one physical depository account that can be utilized
in a variety of ways. Virtual Account Numbers (VANs) allow clients to provide a routing and account
number that can be used to allow ACH transactions to and from a
[Dwolla Balance](/docs/balance-funding-source). The routing and account number is tied to a Dwolla
Client's Master Balance, or one of their
[Verified Customer's](/docs/customer-types#verified-customer) balances. Virtual accounts hold no
funds—they are simply a mechanism to route funds into a single Dwolla Balance. Dwolla's Virtual
Account Number feature performs two crucial functions:
* Eliminate the need to deposit funds into an intermediary depository account before transferring
funds in or out of your Dwolla Balance.
* Keep transaction data separate and organized within the Dwolla Balance.
VANs are a premium feature and count toward a total Funding Source limit. For existing Dwolla
clients, you will be required to implement additional requirements in order to be approved for
production access. To learn more about pricing and enabling this feature, please
contact Sales.
#### Items To Note
* VANs cannot be used as a source or destination when calling the
[/transfers endpoint](/docs/api-reference/transfers/initiate-a-transfer).
* Users that are enabled for VANs will currently not have access to RTP or wire features.
* Many virtual account numbers can route to a single Dwolla Balance. If you need to designate a
portion of funds within a balance, reference the [Labels API](/docs/api-reference/labels).
* Dwolla has no transaction limit for externally initiated transfers.
## Interacting With Virtual Accounts
The Dwolla API supports methods for
[creating a VAN for a Verified Customer](/docs/virtual-account-numbers#create-a-van-funding-source-for-a-customer)
and
[creating a VAN for a Master Account](/docs/virtual-account-numbers#create-a-van-funding-source-for-an-account),
as well as
[retrieving routing details](/docs/api-reference/funding-sources/retrieve-van-account-and-routing-numbers)
and
[removing a virtual account](/docs/api-reference/funding-sources/update-or-remove-a-funding-source).
Once a virtual account is created, a unique account and routing number pair will be available for
use to create external transactions (reference example below). A virtual account will be represented
as a [Funding Source](/docs/api-reference/funding-sources) in the Dwolla API, however, it cannot be
used when calling the Dwolla API to initiate transfers. Your Dwolla Balance is the only funding
source you can use for transfers using the funds in that account within the Dwolla system, and
you'll only be able to use the VAN to initiate transfers affecting the funds in that account from a
third party system. Upon creation of a virtual account as a Funding Source, it will immediately be
available for use with a `verified` status.
A VAN can be [removed](/docs/api-reference/funding-sources/update-or-remove-a-funding-source) at any
time via the API. A new VAN can be created in its place if needed.
#### Example Routing Details Response:
```bash theme={"dark"}
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/funding-sources/e6d68efb-c49b-43db-8867-e1ca58c6ee8c/ach-routing",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "ach-routing"
},
"funding-source": {
"href": "https://api-sandbox.dwolla.com/funding-sources/e6d68efb-c49b-43db-8867-e1ca58c6ee8c",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "funding-source"
}
},
"accountNumber": "9619991490430833",
"routingNumber": "084106768"
}
```
## Tracking Virtual Account Transfers
Externally initiated transactions will be represented by transfers that are automatically created
and available in the transaction listing endpoint when Dwolla is notified of the transaction by the
ACH Network.
Adding funds into a balance will be represented by a new transfer where the **source** is the VAN
funding source and the **destination** is the Customer.
#### Example Response Object
```bash theme={"dark"}
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/transfers/c7149132-c552-ec11-813a-ebf9f1240ece",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
},
"source": {
"href": "https://api-sandbox.dwolla.com/funding-sources/533e2de1-bc59-4301-a081-a431ef023fbd",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "customer"
},
"destination": {
"href": "https://api-sandbox.dwolla.com/customers/af6eb65c-ab64-4510-8ce3-56076b6ab3a9",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "funding-source"
}
},
"id": "c7149132-c552-ec11-813a-ebf9f1240ece",
"status": "processed",
"amount": {
"value": "10.00",
"currency": "USD"
},
"created": "2021-12-01T16:39:06.200Z",
"achDetails": {
"source": {
"addenda": {
"values": [
"addenda"
]
},
"beneficiaryName": "Fake name",
"companyName": "Fake company name",
"companyEntryDescription": "PAYMENT",
"effectiveDate": "2021-12-01",
"postingData": "Fake company name:Fake discretionary data:Fake name",
"routingNumber": "222222226",
"traceId": "222222225926346"
},
"destination": {}
}
}
```
Withdrawals from the Dwolla Balance will be represented by a new transfer where the **source** is the Customer and the **destination** is the VAN funding source.
#### Example Response Object
```bash theme={"dark"}
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/transfers/c7149132-c552-ec11-813a-ebf9f1240ece",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "transfer"
},
"source": {
"href": "https://api-sandbox.dwolla.com/customers/af6eb65c-ab64-4510-8ce3-56076b6ab3a9",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "customer"
},
"destination": {
"href": "https://api-sandbox.dwolla.com/funding-sources/533e2de1-bc59-4301-a081-a431ef023fbd",
"type": "application/vnd.dwolla.v1.hal+json",
"resource-type": "funding-source"
}
},
"id": "c7149132-c552-ec11-813a-ebf9f1240ece",
"status": "processed",
"amount": {
"value": "10.00",
"currency": "USD"
},
"created": "2021-12-01T16:39:06.200Z",
"achDetails": {
"source": {},
"destination": {
"addenda": {
"values": [
"addenda"
]
},
"beneficiaryName": "Fake name",
"companyName": "Fake company name",
"companyEntryDescription": "PAYMENT",
"effectiveDate": "2021-12-01",
"postingData": "Fake company name:Fake discretionary data:Fake name",
"routingNumber": "222222226",
"traceId": "222222225926346"
}
}
}
```
Each new transfer will trigger a `bank_transfer_created` or `customer_bank_transfer_created` webhook. The creation webhooks will be triggered approximately 30 minutes prior to
any completed webhook event of `bank_transfer_completed` or `customer_bank_transfer_completed`.
Transfers will be created in a `pending` or `failed` status depending on whether the customer record
or VAN has been deactivated, suspended, or removed.
Additional details about an external transaction will be available in the achDetails object when
[retrieving the transfer](/docs/api-reference/transfers/retrieve-a-transfer) from the API. Either
the source or the destination will be present in the object depending on the direction of the funds
transfer.
#### ACH Details Optional Fields
| Name | Description |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| addenda | Contains addenda information for the transfer. See [addenda object](#addenda-object) for details. |
| beneficiaryName | Beneficiary of the transaction's name. In general, should match the user onboarded to the Platform's name. |
| companyEntryDescription | Describes the purpose of the transaction. Values can include but are not limited to: `REVERSAL`, `RECLAIM`, `NO CHECK`, `AUTOENROLL`, `REDEPCHECK`, `RETURN FEE`, `RETRY PMNT`, and `HEALTHCARE`. |
| companyId | Numeric identifier of originator. |
| companyName | Name of the originator. |
| effectiveDate | The date when the ACH transaction becomes effective, formatted as `YYYY-MM-DD`. This is typically the settlement date for the transaction. |
| postingData | Suggested memo line format for bank statements, structured as `companyName:companyDiscretionaryData:beneficiaryName`. Provides additional context for transaction identification. |
| routingNumber | Routing number of Originating Depository Financial Institution (ODFI). Identifies the financial institution that originated the ACH transaction. |
| traceId | A unique identifier for tracing the ACH transaction through the banking network. Used for transaction tracking and reconciliation purposes. |
#### Example `achDetails` Object
```bash theme={"dark"}
{
...
"achDetails": {
"source": {
"addenda": {
"values": [
"string"
]
},
"beneficiaryName": "string",
"companyId": "string",
"companyName": "string",
"companyEntryDescription": "PAYMENT", // will differ depending on originator
"effectiveDate": "YYYY-MM-DD",
"postingData": "string:string:string", // suggested memo line, fields are companyName:companyDiscretionaryData:beneficiaryName
"routingNumber": "string", // of originating bank
"traceId": "string"
},
"destination": {
// same fields as source
}
}
}
```
## API Operations
Virtual Account Numbers integrate seamlessly with Dwolla's existing funding source endpoints, requiring only VAN-specific parameters to enable external ACH routing capabilities. Each operation leverages the standard funding source architecture while providing unique account and routing numbers for external transaction processing.
Create a Virtual Account Number for a Dwolla Master Account using the funding source endpoint with `type` set to `virtual`.
Create a Virtual Account Number for a Verified Customer using the customer funding source endpoint with `type` set to `virtual`.
Retrieve VAN funding source details including `ach-routing` link for accessing unique account and routing numbers.
Remove a Virtual Account Number by setting `removed` to `true`. A new VAN can be created if needed.
## Testing
Before deploying to production, validate your Virtual Account Number implementation using Dwolla's Sandbox environment. The sandbox environment provides comprehensive simulation tools for external ACH transfers, failure scenarios, and edge cases to ensure your integration handles real-world payment flows reliably.
Learn how to simulate VAN transfers and test failure scenarios in the Sandbox environment.
# Webhook Events
Source: https://developers.dwolla.com/docs/webhook-events
Real-time event notifications. Determine why a webhook subscription is vital to the experience of your application.
## Overview
When an action occurs within the Dwolla system on a resource (Customers, Transfers, etc.), an Event object is created to record a change to the state of the resource. All created Webhook Events follow the same format and include high level details such as: an event topic, a resource URL that identifies the specific resource that changed states, and a timestamp. If your application has an active [webhook subscription](/docs/working-with-webhooks), all Events relevant to your integration will trigger a webhook notification each time they occur. The following resource article will provide guidance on the structure of webhook events, which will assist with handling incoming webhooks.
Dwolla strongly encourages all applications to establish a webhook
subscription in production. It's a vital tool for receiving timely
notifications about crucial actions within your Dwolla account, ensuring
seamless updates and notifications for your end users.
## Webhook request details
When your webhook subscription is configured, events will be created and sent asynchronously via webhooks as they occur. The webhook notification from Dwolla is a POST request that contains a JSON-encoded payload as well as HTTP headers, which are both used when consuming the webhook. Webhook payloads are designed to be lightweight with only minimum details regarding the triggered event. Dwolla returns links within the Event object pointing to relevant resources in the API which are used to lookup more detailed information on the resource that changed states.
## Webhook headers
There are a few HTTP headers that are useful for your application when consuming the webhook request. `X-Dwolla-Topic` lets your app know, at a high level, the type of event being sent in the payload. `X-Request-Signature-SHA-256` contains an HMAC SHA-256 hash based on the webhook payload and a key which is your webhook secret. The webhook signature should be [processed and validated](/docs/working-with-webhooks#step-2-processing-and-validating-webhooks) prior to parsing the webhook payload.
* `X-Dwolla-Topic` - customer\_created
* `X-Request-Signature-SHA-256` - ed551cfb4acb48d31e14886bffa33aa417dfa4a3d3778f6141a7f7f92ee64861
## Webhook payload
All webhook payloads will include an Event object which will follow the same format as outlined in the [API reference docs](/docs/api-reference/events). An Event contains `_links` to: the relevant resource that caused the Event to be triggered, the Customer that the Event belongs to, and a `self` link to identify the unique Event. In addition to relevant `_links`, the payload will include attributes such as a `created` timestamp, event `topic`, `resourceId`, and conditionally a `correlationId` (see table below for more information).
| Attribute | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| \_links | An object that contains relevant links to resources in the Dwolla API. Possible links can include: `self`, `account`, `resource`, and `customer`.
`self` - a link to the unique event
`account` - a link to the Dwolla account that the application belongs to
`resource` - a link to the resource that changed states. Used to lookup additional details returned on the resource itself
`customer` - a link to the customer the Event belongs to |
| id | Unique Event ID. An Event ID is used along with the created timestamp for idempotent event processing. |
| created | ISO-8601 timestamp when event was created. |
| topic | Type of action that occurred with Dwolla. |
| resourceId | Unique ID of the resource that triggered the Event. |
| correlationId | Unique ID that was specified, if any, when a [transfer was created](/docs/api-reference/transfers/initiate-a-transfer).
**This value is only present for [transfer and transfer-related webhooks](#customers---transfers).** |
### Example webhook payload
```json theme={"dark"}
{
"_links": {
"account": {
"href": "https://api-sandbox.dwolla.com/accounts/0ee84069-47c5-455c-b425-633523291dc3",
"resource-type": "account",
"type": "application/vnd.dwolla.v1.hal+json"
},
"customer": {
"href": "https://api-sandbox.dwolla.com/customers/a6f09251-c2de-4833-94a8-5c70916cebbc",
"resource-type": "customer",
"type": "application/vnd.dwolla.v1.hal+json"
},
"resource": {
"href": "https://api-sandbox.dwolla.com/customers/a6f09251-c2de-4833-94a8-5c70916cebbc",
"type": "application/vnd.dwolla.v1.hal+json"
},
"self": {
"href": "https://api-sandbox.dwolla.com/events/29a82d20-a703-41cb-9b3c-bd409c499925",
"resource-type": "event",
"type": "application/vnd.dwolla.v1.hal+json"
}
},
"created": "2019-05-30T18:21:11.490Z",
"id": "29a82d20-a703-41cb-9b3c-bd409c499925",
"resourceId": "a6f09251-c2de-4833-94a8-5c70916cebbc",
"topic": "customer_created"
}
```
## List of Customer related events by resource
Customer related webhooks are available for the list of Events shown below. As API enhancements are made, Dwolla may add new Events at any point in the future. A complete list of supported Events can be found in the [API docs](/docs/api-reference/events/list-events).
The Event topic for all events triggered for your end users that are created
as Customers will be prepended with customer\_\*. Events that
represent actions being triggered on your primary Dwolla Account are not shown
below. Refer to the API docs for
the complete list of Events.
### Customers
#### `customer_created`
A Customer was created.
**Timing:** Occurs upon a POST request to the [Create a Customer](/docs/api-reference/customers/create-a-customer) endpoint.
#### `customer_kba_verification_needed`
The retry identity verification attempt failed due insufficient scores on the submitted data. The end user will have a single KBA attempt to answer a set of "out of wallet" questions about themselves for identity verification.
**Timing:** Occurs after a failed attempt to verify a Verified Customer Record.
#### `customer_kba_verification_failed`
The end user failed KBA verification and was unable to correctly answer at least three KBA questions.
**Timing:** Triggered after a single attempt at verifying a Verified Customer Record using KBA.
#### `customer_kba_verification_passed`
The end user was able to correctly answer at least three KBA questions.
**Timing:** Triggered after a Verified Customer Record successfully passes KBA requirements.
#### `customer_reverification_needed`
Incomplete information was received for a Customer; updated information is needed to verify the Customer.
**Timing:** Occurs upon a POST request to the [Update a Customer](/docs/api-reference/customers/update-a-customer) endpoint, or when Dwolla places a Customer into retry status.
#### `customer_verification_document_needed`
Additional documentation is needed to verify a Customer.
**Timing:** Occurs when a second attempt to re-verify a Customer fails, which systematically places the Customer in document status immediately after a POST request to the [Update a Customer](/docs/api-reference/customers/update-a-customer) endpoint.
#### `customer_verification_document_uploaded`
A verification document was uploaded for a Customer.
**Timing:** Occurs upon a POST request to the [Create a Document](/docs/api-reference/documents/create-a-document-for-a-customer) endpoint.
#### `customer_verification_document_failed`
A verification document was rejected for a Customer.
**Timing:** Occurs when a document uploaded for a Customer is reviewed by Dwolla, and rejected with a document failure reason, usually within 1-2 business days of uploading a document.
#### `customer_verification_document_approved`
A verification document was approved for a Customer.
**Timing:** Occurs when a document uploaded for a Customer is reviewed by Dwolla, and approved, usually within 1-2 business days of uploading a document.
#### `customer_verified`
A Customer was verified.
**Timing:** Occurs when a Customer is verified by Dwolla upon a POST request to the [Create a Customer](/docs/api-reference/customers/create-a-customer) endpoint. In a case where the Customer isn't instantly verified upon creation, this event occurs when the Customer is verified after a `retry` attempt, or after a document is approved.
#### `customer_suspended`
A Customer was suspended.
**Timing:** Occurs when Dwolla systematically places a Customer in `suspended` status as a result of uploading fraudulent document, or upon receiving certain ACH return codes when a transfer fails.
#### `customer_activated`
A Customer moves from deactivated or suspended to an active status.
**Timing:** Occurs upon reactivating a Customer that has a `deactivated` status by making a POST request to the [Update a Customer](/docs/api-reference/customers/update-a-customer) endpoint, or when Dwolla reactivates a Customer that has a `suspended` status.
#### `customer_deactivated`
A Customer was deactivated.
**Timing:** Occurs upon deactivation of a Customer by making a POST request to the [Update a Customer](/docs/api-reference/customers/update-a-customer) endpoint, or when Dwolla systematically deactivates a Customer upon receiving certain ACH return codes when a transfer fails.
### Customers - Beneficial Owners
#### `customer_beneficial_owner_created`
A Beneficial owner was successfully created.
**Timing:** Occurs upon a POST request to the [Create a beneficial owner](/docs/api-reference/beneficial-owners/create-beneficial-owner) endpoint.
#### `customer_beneficial_owner_removed`
An individual beneficial owner has been successfully removed from the Customer.
**Timing:** Occurs upon a POST request to the [Remove a beneficial owner](/docs/api-reference/beneficial-owners/delete-beneficial-owner) endpoint.
#### `customer_beneficial_owner_verification_document_needed`
Additional documentation is needed to verify an individual beneficial owner.
**Timing:** Occurs when a second attempt to re-verify a beneficial owner fails, which systematically places the beneficial owner in document status immediately after a POST request to the [Update a beneficial owner](/docs/api-reference/beneficial-owners/update-beneficial-owner) endpoint.
#### `customer_beneficial_owner_verification_document_uploaded`
A verification document was uploaded for beneficial owner.
**Timing:** Occurs upon a POST request to the [Create a document for a beneficial owner](/docs/api-reference/documents/create-a-document-for-beneficial-owner) endpoint.
#### `customer_beneficial_owner_verification_document_failed`
A verification document was rejected for a beneficial owner.
**Timing:** Occurs when a document uploaded for a beneficial owner is reviewed by Dwolla, and rejected with a document failure reason, usually within 1-2 business of uploading a document.
#### `customer_beneficial_owner_verification_document_approved`
A verification document was approved for a beneficial owner.
**Timing:** Occurs when a document uploaded for a Customer is reviewed by Dwolla, and approved, usually within 1-2 business days of uploading a document.
#### `customer_beneficial_owner_reverification_needed`
A previously verified beneficial owner status has changed due to either a change in the beneficial owner's information or at request for more information from Dwolla. The individual will need to verify their identity within 30 days.
**Timing:** Occurs upon a POST request to the [Update a beneficial owner](/docs/api-reference/beneficial-owners/update-beneficial-owner) endpoint, or when Dwolla places the beneficial owner into incomplete status.
#### `customer_beneficial_owner_verified`
A beneficial owner has been verified.
**Timing:** Occurs when a Beneficial Owner is verified by Dwolla upon a POST request to the [Create a beneficial owner](/docs/api-reference/beneficial-owners/create-beneficial-owner) endpoint. In a case where the Beneficial Owner isn't instantly verified upon creation, this event occurs when the Beneficial Owner is verified after an update, or after a document is approved.
### Customers - Exchanges
#### `customer_exchange_reauth_required`
An exchange has been deactivated (or is pending deactivation) and requires reauthentication.
**Timing:** Occurs when access to a user's connected bank account has been interrupted. This interruption could be due to changes on the bank's end, such as a password update, multi-factor authentication reset or revoked consent. This event signals that a user's bank connection needs to be refreshed by [creating a reauth exchange session](/docs/api-reference/exchange-sessions/create-re-authentication-exchange-session).
### Customers - Funding Sources
#### `customer_funding_source_added`
A funding source was added to a Customer.
**Timing:** Occurs upon a POST request to the [Create a funding source for a customer](/docs/api-reference/funding-sources/create-customer-funding-source) endpoint, or when a funding source is added via drop-in components or a third party bank verification method.
#### `customer_funding_source_removed`
A funding source was removed from a Customer.
**Timing:** Occurs upon a POST request to the [Remove a funding source](/docs/api-reference/funding-sources/update-or-remove-a-funding-source) endpoint, or when Dwolla systematically removes a funding source upon receiving certain ACH return codes when a transfer fails.
#### `customer_funding_source_verified`
A Customer's funding source was marked as verified.
**Timing:** Occurs upon a POST request to the [Verify micro-deposits](/docs/api-reference/funding-sources/initiate-or-verify-micro-deposits) endpoint with the correct amounts, or when a funding source is added + verified via a third-party bank verification method. Also occurs in cases where Dwolla manually marks a funding source as verified.
#### `customer_funding_source_unverified`
A funding source has been systematically unverified. This is generally a result of a transfer failure.
**Timing:** Occurs when Dwolla systematically marks a funding source unverified upon receiving certain ACH return codes when a transfer fails.
#### `customer_funding_source_negative`
A Customer's balance has gone negative. You are responsible for ensuring a zero or positive Dwolla balance for Customer accounts created by your application. If a Customer's Dwolla balance has gone negative, you are responsible for making the Dwolla Customer account whole. Dwolla will notify you via a webhook and separate email of the negative balance.
**Timing:** Occurs upon a POST request to the [Initiate a transfer](/docs/api-reference/transfers/initiate-a-transfer) endpoint that causes a funding source balance to go negative.
#### `customer_funding_source_updated`
A Customer's funding source has been updated. This can also be fired as a result of a correction after a bank transfer processes.
**Timing:** Occurs upon a POST request to the [Update a funding source](/docs/api-reference/funding-sources/update-or-remove-a-funding-source) endpoint. For example, a financial institution can issue a correction to change the bank account type from checking to savings.
#### `customer_microdeposits_added`
Two less than or equal to ten cent transfers to a Customer's linked bank account were initiated.
**Timing:** Occurs upon a POST request to the [Initiate micro-deposits](/docs/api-reference/funding-sources/initiate-or-verify-micro-deposits) endpoint.
#### `customer_microdeposits_failed`
The two less than or equal to ten cent transfers to a Customer's linked bank account failed to clear successfully.
**Timing:** Occurs when micro-deposits fail to clear into a bank account, usually within 1-2 business days of initiating them.
#### `customer_microdeposits_completed`
The two less than or equal to ten cent transfers to a Customer's linked bank account were successful.
**Timing:** Occurs when micro-deposits are successful, usually within 1-2 business days of initiating them.
#### `customer_microdeposits_maxattempts`
The Customer has reached their max verification attempts, limit of three. The Customer can no longer verify their funding source with the completed micro-deposit amounts.
**Timing:** Occurs upon the third POST request to the [Verify micro-deposits](/docs/api-reference/funding-sources/initiate-or-verify-micro-deposits) endpoint with incorrect micro-deposit amounts.
### Customers - Transfers
For transfer webhooks, in addition to the [default payload keys](#webhook-payload), a `correlationId` key-value pair *may* be present if a value was specified when the [transfer was created](/docs/api-reference/transfers/initiate-a-transfer).
The event descriptions below reflect ACH behavior. For **Instant Payments**, the recipient always receives `customer_bank_transfer_*` events regardless of whether the recipient is a Receive-only User, an Unverified Customer, or a Verified Customer. See [Instant Payments webhook notifications](/docs/instant-payments#webhook-notifications) for details.
#### `customer_bank_transfer_created`
A bank transfer was created for a Customer. Represents funds moving either from a verified Customer's bank to the Dwolla Platform or from the Dwolla Platform to a verified Customer's bank.
**Timing:** Occurs upon a POST request to the [Initiate a transfer](/docs/api-reference/transfers/initiate-a-transfer) endpoint when sending funds from a Verified Customer's bank, or when funds move from a receiving Verified Customer's balance to their bank.
#### `customer_bank_transfer_cancelled`
A pending Customer bank transfer has been cancelled, and will not process further. Represents a cancellation of funds either transferring from a verified Customer's bank to the Dwolla Platform or from the Dwolla Platform to a verified Customer's bank.
**Timing:** Occurs upon a POST request to the [Cancel a transfer](/docs/api-reference/transfers/cancel-a-transfer) endpoint, or when Dwolla manually cancels a transfer.
#### `customer_bank_transfer_failed`
A Customer bank transfer failed. Usually, this is a result of an ACH failure (insufficient funds, etc.). Represents a failed funds transfer either from a verified Customer's bank to the Dwolla Platform or from the Dwolla Platform to a verified Customer's bank.
**Timing:** Occurs when Dwolla marks a transfer as failed.
#### `customer_bank_transfer_creation_failed`
Transfers initiated to a verified Customer's bank must pass through the verified Customer's balance before being sent to a receiving bank. Dwolla will fail to create a transaction intended for a verified Customer's bank if the funds available in the balance are less than the transfer amount.
**Timing:** Occurs when a transfer to a verified Customer's bank fails to be created.
#### `customer_bank_transfer_completed`
A bank transfer that was created for a Customer was successful. Represents a successful funds transfer either from a verified Customer's bank to the Dwolla Platform or from the Dwolla Platform to a verified Customer's bank.
**Timing:** Occurs when a funds transfer into the Dwolla Platform or a verified Customer's bank is successful, based on the transfer processing timing used.
#### `customer_transfer_created`
A transfer was created for a Customer. Represents funds transferring from a verified Customer's balance or unverified Customer's bank.
**Timing:** Occurs upon a POST request to the [Initiate a transfer](/docs/api-reference/transfers/initiate-a-transfer) endpoint when sending funds from a verified Customer's balance, or to/from an unverified Customer's bank.
#### `customer_transfer_cancelled`
A pending transfer has been cancelled, and will not process further. Represents a cancellation of funds transferring either to an unverified Customer's bank or to a verified Customer's balance.
**Timing:** Occurs upon a POST request to the [Cancel a transfer](/docs/api-reference/transfers/cancel-a-transfer) endpoint to cancel a transfer initiated from a verified Customer's balance, or to/from an unverified Customer's bank.
#### `customer_transfer_failed`
A Customer transfer failed. Represents a failed funds transfer either to an unverified Customer's bank or to a verified Customer's balance.
**Timing:** Occurs when Dwolla marks a transfer as failed.
#### `customer_transfer_completed`
A Customer transfer was successful. Represents a successful funds transfer either to an unverified Customer's bank or to a verified Customer's balance.
**Timing:** Occurs when a funds transfer into an unverified Customer's bank or a verified Customer's balance is successful, based on the transfer processing timing used.
### Customers - Mass Payments
#### `customer_mass_payment_created`
A verified Customer's mass payment was created.
**Timing:** Occurs upon a POST request to the [Initiate a mass-payment](/docs/api-reference/mass-payments/initiate-a-mass-payment) endpoint.
#### `customer_mass_payment_completed`
A verified Customer's mass payment was completed. However, this doesn't mean that each mass payment item's transfer was successful.
**Timing:** Occurs when a mass payment job completes.
#### `customer_mass_payment_cancelled`
A Verified Customer's created and deferred mass payment was cancelled.
**Timing:** Occurs upon a POST request to the Update a mass-payment endpoint when cancelling a mass payment job.
#### `customer_balance_inquiry_completed`
Upon checking a Customer's bank balance, Dwolla will immediately return an HTTP 202 with response body that includes a status of `processing`.
**Timing:** This event will be triggered when the bank balance check has completed processing.
### Customers - Labels
#### `customer_label_created`
A Verified Customer's label was created.
**Timing:** Occurs upon a POST request to the [Create a label](/docs/api-reference/labels/create-a-label-for-a-customer) endpoint.
#### `customer_label_ledger_entry_created`
A ledger entry for a Verified Customer's label was created.
**Timing:** Occurs upon a POST request to the [Create a label ledger entry](/docs/api-reference/labels/create-a-label-ledger-entry) endpoint.
#### `customer_label_removed`
A Verified Customer's label was removed.
**Timing:** Occurs upon a POST request to the [Remove a label](/docs/api-reference/labels/remove-a-label) endpoint.
# What is Dwolla?
Source: https://developers.dwolla.com/docs/what-is-dwolla
Leverage Dwolla's existing financial institution relationship to connect your business to the U.S. banking infrastructure.
## Overview
Dwolla offers a white labeled product experience powered by an API that enables you to embed account-to-account payments into a web or mobile application. Dwolla connects your business to the U.S. banking infrastructure through an established relationship with one of Dwolla's financial institution partners. The platform revolves around the idea of stored-value/digital wallet functionality, enabling a range of funds flow models such as sending funds (payouts), receiving funds (pay-ins), facilitating transfers between users, and facilitating transfers between a user's payment accounts (Me-to-Me).
## Audience and Technical Level
Dwolla is intended for businesses of all sizes, from small startups to large enterprises. The technical level required to integrate can vary depending on the specific needs of your business and which aspects of the product you'll be utilizing. In general, you will need to have a basic understanding of HTTP requests and responses, JSON data format, and security best practices. Dwolla requires skills in both front-end and back-end web and/or mobile development. Since Dwolla offers a REST API, you can integrate the API into your own platform using standard programming languages such as Node.js, Python, PHP, C-Sharp, Java/Kotlin, and Ruby. To expedite your integration, low code Drop-in Components are available for a wide range of functions which can be customized to match your brand.
## Benefits
The benefits of using Dwolla include:
* Fast and secure payments: Dwolla uses the Automated Clearing House (ACH) network to process payments, which is a secure and reliable way to transfer money.
* Low fees: Dwolla's transaction fees are lower than those of traditional payment processors; such as credit cards.
* Flexibility: Dwolla's API is flexible and can be customized to meet the needs of businesses of all sizes. Real-time webhooks enable your application to be in sync with all events that occur on the Dwolla platform.
* Scalability and reliability: Dwolla is scalable and can handle large volumes of transactions. In addition, the API maintains 99.9% uptime so you can count on reliability as your business grows.
## Features
The following key features help you diversify the way in which you embed account-to-account payments into your own platform:
* Same-day ACH payments: Allows you to expedite the way you send and receive money via ACH by opening up additional windows for processing transactions on the same day.
* Real-time payments: Dwolla also supports real-time payments, which allow businesses to send instantly. You can pre-fund your [Dwolla Balance](/docs/balance-funding-source) to enable near real-time push payments 24/7/365.
* Reporting and analytics: Dwolla provides businesses with detailed reporting and analytics data, which can be used to track payments and improve cash flow management.
## Limitations
Some of the limitations of Dwolla include:
* Not available in all countries: Dwolla is currently only available for businesses registered to operate within the United States.
* Integration complexity: For complex payment flows, Dwolla can be challenging to set up for businesses that are not familiar with APIs.
# Working with Webhooks
Source: https://developers.dwolla.com/docs/working-with-webhooks
Implement simple, real-time, event notifications regarding account and transaction status, and more.
## Overview
A webhook is a means of notifying your application of the occurrence of an event with some relevant information. [Events](/docs/api-reference/events) are created each time a resource is created or updated in Dwolla. For example, when a [Customer is created](/docs/api-reference/customers/create-a-customer) or a [Funding Source is removed](/docs/api-reference/funding-sources/update-or-remove-a-funding-source), a `customer_created` and `customer_funding_source_removed` event will be created, respectively. These events are what trigger HTTP webhook requests to your subscribed URL if you have an active webhook subscription. It is important to note that a single API request can trigger multiple webhooks to be fired, e.g. [initiating a transfer](/docs/api-reference/transfers/initiate-a-transfer) from an Account to Customer can create the events `transfer_created` and `customer_transfer_created`.
Check out our API reference for an exhaustive list of all possible events.
## Webhook Event
Each webhook contains an [Event](/docs/api-reference/events) with `_links` to the following resources:
* The unique event itself
* The [Dwolla Account](/docs/api-reference/accounts) associated with the event
* The Resource associated with the event
* The [Customer](/docs/api-reference/customers) that the resource relates to (if applicable)
Example webhook payload:
```json theme={"dark"}
{
"id": "80d8ff7d-7e5a-4975-ade8-9e97306d6c15",
"resourceId": "36E9DCB2-889B-4873-8E52-0C9404EA002A",
"topic": "customer_created",
"timestamp": "2015-10-22T14:44:11.407Z",
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/events/80d8ff7d-7e5a-4975-ade8-9e97306d6c15"
},
"account": {
"href": "https://api-sandbox.dwolla.com/accounts/b4cdac07-eeca-4059-a29c-48900e453d54"
},
"resource": {
"href": "https://api-sandbox.dwolla.com/customers/36E9DCB2-889B-4873-8E52-0C9404EA002A"
},
"customer": {
"href": "https://api-sandbox.dwolla.com/customers/36E9DCB2-889B-4873-8E52-0C9404EA002A"
}
}
}
```
For detailed information on Dwolla's webhook request structure, refer to the Webhook Events resource.
## What to Know About Dwolla Webhooks
* Each application can have multiple webhook subscriptions associated with it. While one subscription is sufficient, you can create up to **five** in Production and **ten** in Sandbox for redundancy.
* Webhooks are sent asynchronously and are not guaranteed to be delivered in order. We recommend that applications protect against duplicated events by [making event processing idempotent](/docs/working-with-webhooks#check-for-duplicate-events).
* Your application will need to respond to Dwolla webhook requests with a 200-level HTTP status code within 10 seconds of receipt. Otherwise, the attempt will be counted as a failure and Dwolla will retry sending the webhook according to the [back-off schedule](/docs/api-reference/webhook-subscriptions).
* If there are 400 consecutive failures, and it has been 24 hours since your last success, your webhook subscription will be automatically paused and an email will be sent to the Admin of the Dwolla account. After fixing the issue that is causing the failures, you can unpause the webhook subscription either via your Dashboard or [Dwolla's API](/docs/api-reference/webhook-subscriptions/update-a-webhook-subscription) in order to continue receiving new webhooks and to [retry failed webhooks](/docs/api-reference/webhooks/retry-a-webhook).
## Getting Started
In this guide we will walk through creating a webhook subscription along with validating and processing webhook requests.
You will need to have a [Sandbox account](https://accounts-sandbox.dwolla.com/sign-up) already set up. Although not required, this guide assumes that you have some familiarity with Amazon Web Services (AWS); specifically [Lambda](https://aws.amazon.com/lambda/) and [SQS](https://aws.amazon.com/sqs/), or other similar services.
# Step 1 - Create a Webhook Subscription
First, you will need to have a URL that is publicly accessible where Dwolla can send webhooks in the form of HTTP requests. This also means that anyone on the Internet can hit your endpoint. As such, here are some security concerns:
* Your webhook endpoint should only be accessible over [TLS (HTTPS)](https://www.dwolla.com/updates/improving-transport-layer-security/) and your server should have a valid SSL certificate.
* Your subscription should include a random, secret key, only known by your application. This secret key should be securely stored and used later when [validating the authenticity of the webhook request](#authentication) from Dwolla.
#### Request Parameters
| Parameters | Required | Type | Description |
| ---------- | -------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| url | yes | string | The publicly-accessible URL where Dwolla should deliver the webhook notification. |
| secret | yes | string | A random, secret key, only known by your application. This secret key should be securely stored and used later when [validating the authenticity of the webhook](#authentication) from Dwolla. |
##### Request and Response
```raw HTTP theme={"dark"}
POST https://api-sandbox.dwolla.com/webhook-subscriptions
Accept: application/vnd.dwolla.v1.hal+json
Content-Type: application/vnd.dwolla.v1.hal+json
Authorization: Bearer 0Sn0W6kzNicvoWhDbQcVSKLRUpGjIdlPSEYyrHqrDDoRnQwE7Q
{
"url": "https://myapplication.com/webhooks",
"secret": "sshhhhhh"
}
...
HTTP/1.1 201 Created
Location:
https://api-sandbox.dwolla.com/webhook-subscriptions/077dfffb-4852-412f-96b6-0fe668066589
```
When the webhook subscription is created, you will receive a `201 Created` HTTP response with an empty response body. You can refer to the `Location` header to retrieve a link to the newly-created subscription.
## Webhook Subscription Resource
| Parameter | Description |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| id | Unique webhook subscription identifier assigned by Dwolla. |
| url | Subscribed URL where Dwolla will deliver webhook notifications. |
| paused | A boolean `true` or `false` value indicating if a webhook subscription is paused. A webhook subscription will be automatically paused after 400 consecutive failures. In addition, a subscription can be paused or unpaused by calling [this endpoint](/docs/api-reference/webhook-subscriptions/update-a-webhook-subscription) in our API. |
| created | [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) timestamp this webhook subscription was created |
### Request and Response
```raw theme={"dark"}
GET https://api-sandbox.dwolla.com/webhook-subscriptions/077dfffb-4852-412f-96b6-0fe668066589
Accept: application/vnd.dwolla.v1.hal+json
Authorization: Bearer pBA9fVDBEyYZCEsLf/wKehyh1RTpzjUj5KzIRfDi0wKTii7DqY
...
{
"_links": {
"self": {
"href": "https://api-sandbox.dwolla.com/webhook-subscriptions/077dfffb-4852-412f-96b6-0fe668066589"
},
"webhooks": {
"href": "https://api-sandbox.dwolla.com/webhook-subscriptions/077dfffb-4852-412f-96b6-0fe668066589/webhooks"
}
},
"id": "077dfffb-4852-412f-96b6-0fe668066589",
"url": "https://myapplication.com/webhooks",
"created": "2022-01-20T16:20:47+00:00"
}
```
# Step 2 - Processing and Validating Webhooks
Before we begin, although not required, please note that many of the snippets we use include Amazon Web Services (AWS) as a dependency — specifically, [Lambda](https://aws.amazon.com/lambda/) and [SQS](https://aws.amazon.com/sqs/). We will use AWS' [v2 Node SDK](https://www.npmjs.com/package/aws-sdk); however, [v3 is also currently available](https://github.com/aws/aws-sdk-js-v3).
Now that you have created a webhook subscription in the previous step, we will work on creating a webhook listener/handler. In order to asynchronously process webhooks, we recommend implementing the four following steps: [**ingestion**](#ingestion), [**authentication**](#authentication), [**queueing**](#queueing) and [**processing**](#processing).
## Ingestion
### Receive the Webhook
At its core, a webhook listener is an HTTP endpoint that Dwolla will call. As such, your endpoint must:
* Be publicly accessible (Dwolla cannot send requests to `localhost`)
* Be able to receive `POST` HTTP requests (Dwolla will not send `GET`, `PUT`, `PATCH`, etc.)
* Have TLS enabled (with a valid SSL certificate issued to your domain)
* Be able to handle at least 10 concurrency requests, unless a lower value has otherwise been configured for your application by Dwolla
### Return HTTP 2xx Status Code
Once a webhook has fired to your endpoint (and you have received it), in order to fully "ingest" the webhook, you must respond to Dwolla with a 200-level status code (e.g., `200 OK`). Additionally, Dwolla must receive your response within 10,000 milliseconds (or, in other words, 10 seconds). If a response is not received in the allotted time, Dwolla will retry the request up to 8 times over the next 72 hours, according to our [back-off schedule](/docs/api-reference/webhook-subscriptions). Finally, if your application fails to response after 400 consecutive attempts and 24 hours has passed since the last success, [your webhook subscription will automatically pause](/docs/api-reference/webhook-subscriptions).
## Authentication
When you [set up your webhook subscription](#step-1-create-a-webhook-subscription), you sent over a `secret` value to Dwolla. This value is a shared secret that only your application and Dwolla should have access to. Before Dwolla sends a webhook, the JSON-encoded payload is used in conjunction with the shared `secret` to create the `X-Request-Signature-SHA-256` header value, which is sent with the webhook request.
When a webhook request is received, Dwolla recommends creating a SHA-256 HMAC signature of the JSON-encoded payload that your application receives with the shared `secret` as the key, and checking its value against the signature that Dwolla generated and supplied in the `X-Request-Signature-SHA-256` header.
```javascript theme={"dark"}
const crypto = require("crypto");
const isSignatureValue = (body, signature) =>
signature ===
crypto
.createHmac("sha256", process.env.DWOLLA_WEBHOOK_SECRET)
.update(body)
.digest("hex");
```
### Considerations and Limitations
When implementing webhook authentication in your application, please consider the following:
* Dwolla uses a highly dynamic and wide range of IP addresses, meaning that your application cannot use IP whitelisting to authenticate webhook requests.
* The request body is already JSON-encoded prior to being sent. As such, the JSON body should never be re-encoded! If it is, the signatures will not match and authentication will fail, even if the request came from Dwolla.
* Your webhook endpoint must have access to the request body in order to authenticate the request. This means that some services, such as AWS Lambda Authorizers, are unable to authenticate a webhook Dwolla.
## Queueing
Once you ingest the webhook and authenticate that it is from Dwolla, instead of handling the business logic within the listener, we recommend passing the webhook off to a queue to be picked up by another Lambda function. By doing this, your webhook listener takes on the minimum amount of responsibility necessary, enabling your application to scale quickly and easily based on the volume of concurrent requests.
Which messaging broker you use is up to you; however, we recommend using [AWS SQS](https://aws.amazon.com/sqs/) or [GCP Pub/Sub](https://cloud.google.com/pubsub) if your application resides in a serverless environment. If your application is self-hosted, [Redis](https://redis.io/) is also a good alternative to the options listed above.
As an example, the following snippet will demonstrate how a webhook could be placed on an AWS SQS queue. In it `QueueUrl` would be the URL of your SQS queue, whereas `MessageBody` would be the "stringified" body of the webhook that Dwolla sends. (For more information on sending a message using AWS SQS, we recommend checking out their [official documentation](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/sqs-examples-send-receive-messages.html#sqs-examples-send-receive-messages-sending).)
```javascript theme={"dark"}
const { SQS } = require("aws-sdk");
const sqs = new SQS();
try {
await sqs
.sendMessage({
QueueUrl: "AWS_SQS_URL",
MessageBody: JSON.stringify("WEBHOOK_JSON_BODY"),
})
.promise();
} catch (e) {
console.error("Failed to place webhook in SQS queue", e);
}
```
## Processing
Now that the webhook has been ingested, authenticated, and queued, once the webhook has been fetched from the queue, we recommend checking for duplicate events and then handling any application-specific business logic.
### Check for Duplicate Events
Although Dwolla tries to only send a webhook once, it is possible that the same webhook may be sent more than once. Because of this, it is recommended to check an internal database against the webhook's [Event](/docs/api-reference/events) ID to ensure that the same business logic is not processed multiple times.
In other words, we recommend maintaining an internal database that keeps track of all of the events that are processed. Then, when a new webhook is received, your application ensures that the event (checked against its ID) has not already been processed previously. Additionally, once your application finishes processing an event, its ID is appended to your database, ensuring that if your application receives another webhook with the same event ID, it will not get processed a second time.
Finally, when checking if an event has already been processed, it's important to note that multiple events can fire for the same resource while still remaining unique. For example, when a transfer is created, there may be cases where you receive `customer_transfer_created` twice with a different event ID but the same resource ID. This is because when a transfer is created, an event is triggered for both the sender and the receiver. It is for this reason that we recommend checking webhooks against their event ID, not by their resource ID or topic.
When your application detects that you received an event that has already been
processed, it is imperative that it returns with a 200-level status, such as
200 OK. If it responds with another value like
409 Conflict, this simply propagates irrelevant information and
may result in your webhook getting automatically paused.
### Handle Business Logic
Once you reach this point, you're finally ready to handle any business logic related to the webhook — for example, creating or updating a database entry, sending a notification email or triggering an alert.
Although business logic will vary case by case based on your application's specific needs, in this final section, we will demonstrate how you can use an AWS Lambda function to pull the webhook off an SQS queue, and print it to the console.
```javascript theme={"dark"}
module.exports.queueHandler = async (event) => {
try {
event.Records.forEach((record) => {
const webhook = JSON.parse(record.body);
console.log(
`Received ${webhook.topic}, body=${JSON.stringify(webhook, null, 2)}`
);
});
} catch (e) {
console.error(
"An unexpected error occurred while processing the queue.",
e
);
}
};
```
## Conclusion
We hope that this guide is helpful in getting your webhook listener/handler set up and configured for use with Dwolla! If you would like to take a closer look at the code that we used (or give it a try using your own account), check out our [webhook-receiver](https://github.com/Dwolla/webhook-receiver) repository on GitHub.
# Frequently Asked Questions
-
Event — An event is a unique resource
that gets created whenever an action occurs in Dwolla that changes the
state of an API resource like a Customer being created or a funding source
being verified.
-
Webhook Subscription —
A webhook subscription is a resource in the API that you can create in
order to subscribe to Dwolla webhooks.
-
Webhook — A webhook is an HTTP
request that Dwolla sends to your subscribed URL to notify your app of an
event. In order to get webhook notifications, you will need to have an
active webhook subscription.
While a webhook subscription is not required for you to integrate with the
API, Dwolla requires all applications to have one in production for
automated notifications of events to your application. Webhooks provide
automatic near real-time status updates to your application versus polling
the API which causes unnecessary load on your application and the API.
Dwolla includes a X-Request-Signature-SHA-256 header on each
webhook request which is a SHA-256 HMAC hash of the request body with the
key being the webhook secret you passed in when you created the
webhook subscription. As a best practice, we recommend validating webhooks
by generating the same SHA-256 HMAC hash and comparing it to the signature
sent with the payload.
We do not recommend nor support relying on IP whitelisting as a method of validating
webhooks. Dwolla's IPs are dynamically allocated with no defined range and are
subject to change. Refer to the Processing/Validating section for a more detailed
guide.
Dwolla sends webhooks for all events that occur in your platform and there
isn't a way to filter what events you subscribe to.
Dwolla automatically
pauses a webhook subscription
after 400 consecutive failed delivery attempts and sends an email to notify
the Admin of the Dwolla account. While it's paused, Dwolla isn't able to
send webhooks for new events to your URL. To resume webhooks, you need to
address the issue that's causing failures
,
unpause the subscription
and
retry missed webhooks
.
No. If your webhook subscription was previously paused or unavailable,
Dwolla will not attempt to re-deliver webhooks. In this case, you can list all webhooks by subscription, filtering all that have zero attempts (in other words, the `attempts` array is empty), and retrying each webhook individually
.
When
listing all webhooks by webhook subscription
, an embedded attempts array will include a
response
object that includes a statusCode property indicating the
HTTP status code that Dwolla received when the webhook attempt was made. If the
status code that Dwolla received was >=300, then the attempt was considered to
have failed.
Yes. You can create up to 5 webhook subscriptions in Production and 10 in
Sandbox. While only one subscription is needed to be notified of all events,
you can have multiple in case one or more of your URLs becomes unreachable.
Dwolla's platform uses asynchronous processing to ensure high availability and performance at scale. During periods of elevated activity, there can be brief intervals where webhook notifications are delivered slightly ahead of when the updated status becomes available via the API.
To build a resilient integration, we recommend the following when processing webhooks:
-
Retry Logic — If an API call returns a status different from what the webhook indicated, implement retries with exponential backoff.
-
Retry Window — Retry for up to 5 minutes using exponential delays (e.g., 5 seconds, 15 seconds, 45 seconds, etc.).
-
Graceful Handling — Design your system to handle brief delays between webhook delivery and API consistency. This could be a short wait before retrying, or rejecting the webhook request so that Dwolla retries it according to its retry schedule.
# Dwolla Developers
Source: https://developers.dwolla.com/index
Build, test, and launch payment solutions with Dwolla's comprehensive API documentation and developer tools.
Infrastructure
API Layer
Your Brand
✓
Payment Sent
\$225.00
Just now
ACH
Same-Day
99.9% uptime
126M+ annual transaction volume
ACH, Same-Day ACH, RTP, FedNow, Push to Card
16+ years of payment experience
Guides and Concepts
Foundational documentation, quickstarts, and implementation guidance organized around how teams actually build.
API Reference
Endpoints, request formats, and response examples for customers, funding sources, transfers, and webhooks.
SDKs and Tools
Official SDKs for TypeScript, PHP, Python, Ruby, C#, and Kotlin/Java, plus Postman collections and developer tooling to accelerate integration.
Platform capabilities
Everything you need to build powerful payment experiences
Dwolla's API provides a complete toolkit for account-to-account transfers, customer verification, and payment management.
Account-to-Account Transfers
Initiate money movement between bank accounts with a unified API.
ACH, Instant payments, and Wire transfers
Flexible configuration of payment speed
Real-time transfer status updates
Flexible End-user Types
Support various customer profiles with different verification levels.
Personal and Business — Identity Verified, Unverified, and Receive-only Users
Tailored onboarding requirements by end-user type
Progressive KYC based on user needs
Open Banking Services
Connect to bank accounts securely with open banking APIs.
Instant account verification
Real-time bank account balance check
Enhanced user experience
Webhooks
Real-time event notifications.
Transfer status notifications
Customer verification updates
Secure signature verification
Security & Compliance
Enterprise-grade security features.
SOC 2 Type 2 compliant
Tokenization for sensitive data
API key & OAuth authentication
Developer Tools
Resources to accelerate development.
SDKs for popular languages
Sandbox testing environment
API explorer & request builder
Same-Day ACH
Same business day
Financial institutions play an important role in our network.
All funds transfers made using the Dwolla Platform are performed by a financial institution partner, and any funds held in a Dwolla Balance are held by a financial institution partner. Learn more about our financial institution partners.