RTP Payouts
API V2 endpoints for managing customer bank accounts and sending instant RTP / FedNow payouts
Overview
The RTP (Real-Time Payments) endpoints let you save US bank accounts on a customer profile and push instant payouts to them via Airwallex over the RTP and FedNow rails. The flow is always:
- Add a bank account for the customer (one-time setup).
- (Optional) List the customer's bank accounts to fetch a
bank_account_id. - Send a payout to that bank account from your vendor
credit_wallet_balance.
Payouts are funded from your vendor credit_wallet_balance. If the Airwallex transfer fails for any reason, the funds are automatically reversed back into your credit_wallet_balance.
List Customer Bank Accounts
List all bank accounts saved for one of your customers.
Endpoint
GET /api/v2/customer/{customer_id}/bank-accounts?api_key=your_api_keyPath Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_id | string | Yes | The unique customer ID (UUID) |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
api_key | string | Yes | Your vendor API key |
Example Request
curl "https://app.safepays.com/api/v2/customer/550e8400-e29b-41d4-a716-446655440000/bank-accounts?api_key=your_api_key"const customerId = '550e8400-e29b-41d4-a716-446655440000';
const apiKey = 'your_api_key';
const response = await fetch(
`https://app.safepays.com/api/v2/customer/${customerId}/bank-accounts?api_key=${apiKey}`
);
const result = await response.json();
console.log(`Found ${result.total} bank account(s)`);import requests
customer_id = '550e8400-e29b-41d4-a716-446655440000'
api_key = 'your_api_key'
url = f'https://app.safepays.com/api/v2/customer/{customer_id}/bank-accounts?api_key={api_key}'
response = requests.get(url)
result = response.json()
print(f"Found {result['total']} bank account(s)")Success Response
Status Code: 200 OK
{
"status": "success",
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"total": 1,
"bank_accounts": [
{
"id": "f3a1b2c4-1234-4abc-9def-1234567890ab",
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"entity_type": "PERSONAL",
"email": "john@example.com",
"address": {
"country_code": "US",
"state": "CA",
"city": "San Francisco",
"postcode": "94103",
"street_address": "123 Main St"
},
"bank_details": {
"account_name": "John Doe",
"account_number": "1234567890",
"routing_number": "111000025",
"bank_name": "Bank of America",
"account_category": "Checking"
},
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1990-01-15",
"created_at": "2026-04-19 14:30:00"
}
]
}Response Fields
| Field | Type | Description |
|---|---|---|
customer_id | string | The customer the bank accounts belong to |
total | integer | Number of bank accounts returned |
bank_accounts | array | List of saved bank account objects (use id as the bank_account_id when creating a payout) |
Error Responses
| Status | Error |
|---|---|
| 400 | "api_key parameter is required" |
| 401 | "Invalid API Key" |
| 403 | "This vendor account has been banned." |
| 404 | "Customer not found" |
Add Customer Bank Account
Save a new US bank account (ACH / FedNow) for a customer. The account can later be used as a payout destination via Payout to Customer Bank Account.
Endpoint
POST /api/v2/customer/{customer_id}/bank-accountRequest Headers
Content-Type: application/jsonRequest Body
{
"api_key": "your_api_key",
"entity_type": "PERSONAL",
"email": "john@example.com",
"address": {
"country_code": "US",
"state": "CA",
"city": "San Francisco",
"postcode": "94103",
"street_address": "123 Main St"
},
"bank_details": {
"account_name": "John Doe",
"account_number": "1234567890",
"routing_number": "111000025",
"bank_name": "Bank of America",
"account_category": "Checking"
},
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1990-01-15"
}| Field | Required | Default | Description |
|---|---|---|---|
api_key | Yes | — | Your vendor API key |
entity_type | No | "PERSONAL" | "PERSONAL" or "COMPANY" |
email | Yes | — | Account holder email |
address.country_code | No | "US" | ISO 2-letter country code |
address.state | Yes | — | State / Province |
address.city | Yes | — | City |
address.postcode | Yes | — | Postal / ZIP code |
address.street_address | Yes | — | Street address |
bank_details.account_name | Yes | — | Name on bank account |
bank_details.account_number | Yes | — | Bank account number |
bank_details.routing_number | Yes | — | ABA routing number |
bank_details.bank_name | Yes | — | Bank name |
bank_details.account_category | No | "Checking" | "Checking" or "Savings" |
first_name | Yes* | — | Required when entity_type = "PERSONAL" |
last_name | Yes* | — | Required when entity_type = "PERSONAL" |
date_of_birth | No | "" | YYYY-MM-DD |
company_name | Yes* | — | Required when entity_type = "COMPANY" |
* Conditional fields depend on entity_type. Personal accounts must include first_name and last_name; company accounts must include company_name.
Example: Personal Account
curl -X POST https://app.safepays.com/api/v2/customer/550e8400-e29b-41d4-a716-446655440000/bank-account \
-H "Content-Type: application/json" \
-d '{
"api_key": "your_api_key",
"entity_type": "PERSONAL",
"email": "john@example.com",
"address": {
"country_code": "US",
"state": "CA",
"city": "San Francisco",
"postcode": "94103",
"street_address": "123 Main St"
},
"bank_details": {
"account_name": "John Doe",
"account_number": "1234567890",
"routing_number": "111000025",
"bank_name": "Bank of America",
"account_category": "Checking"
},
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1990-01-15"
}'const customerId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
`https://app.safepays.com/api/v2/customer/${customerId}/bank-account`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: 'your_api_key',
entity_type: 'PERSONAL',
email: 'john@example.com',
address: {
country_code: 'US',
state: 'CA',
city: 'San Francisco',
postcode: '94103',
street_address: '123 Main St'
},
bank_details: {
account_name: 'John Doe',
account_number: '1234567890',
routing_number: '111000025',
bank_name: 'Bank of America',
account_category: 'Checking'
},
first_name: 'John',
last_name: 'Doe',
date_of_birth: '1990-01-15'
})
}
);
const result = await response.json();
console.log('Bank Account ID:', result.bank_account.id);import requests
import json
customer_id = '550e8400-e29b-41d4-a716-446655440000'
url = f'https://app.safepays.com/api/v2/customer/{customer_id}/bank-account'
headers = {'Content-Type': 'application/json'}
data = {
'api_key': 'your_api_key',
'entity_type': 'PERSONAL',
'email': 'john@example.com',
'address': {
'country_code': 'US',
'state': 'CA',
'city': 'San Francisco',
'postcode': '94103',
'street_address': '123 Main St'
},
'bank_details': {
'account_name': 'John Doe',
'account_number': '1234567890',
'routing_number': '111000025',
'bank_name': 'Bank of America',
'account_category': 'Checking'
},
'first_name': 'John',
'last_name': 'Doe',
'date_of_birth': '1990-01-15'
}
response = requests.post(url, headers=headers, data=json.dumps(data))
result = response.json()
print(f"Bank Account ID: {result['bank_account']['id']}")Example: Company Account
curl -X POST https://app.safepays.com/api/v2/customer/550e8400-e29b-41d4-a716-446655440000/bank-account \
-H "Content-Type: application/json" \
-d '{
"api_key": "your_api_key",
"entity_type": "COMPANY",
"email": "ap@acme.com",
"address": {
"country_code": "US",
"state": "NY",
"city": "New York",
"postcode": "10001",
"street_address": "200 Broadway"
},
"bank_details": {
"account_name": "Acme Inc",
"account_number": "9876543210",
"routing_number": "021000021",
"bank_name": "Chase",
"account_category": "Checking"
},
"company_name": "Acme Inc"
}'const customerId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
`https://app.safepays.com/api/v2/customer/${customerId}/bank-account`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: 'your_api_key',
entity_type: 'COMPANY',
email: 'ap@acme.com',
address: {
country_code: 'US',
state: 'NY',
city: 'New York',
postcode: '10001',
street_address: '200 Broadway'
},
bank_details: {
account_name: 'Acme Inc',
account_number: '9876543210',
routing_number: '021000021',
bank_name: 'Chase',
account_category: 'Checking'
},
company_name: 'Acme Inc'
})
}
);
const result = await response.json();
console.log('Bank Account ID:', result.bank_account.id);import requests
import json
customer_id = '550e8400-e29b-41d4-a716-446655440000'
url = f'https://app.safepays.com/api/v2/customer/{customer_id}/bank-account'
headers = {'Content-Type': 'application/json'}
data = {
'api_key': 'your_api_key',
'entity_type': 'COMPANY',
'email': 'ap@acme.com',
'address': {
'country_code': 'US',
'state': 'NY',
'city': 'New York',
'postcode': '10001',
'street_address': '200 Broadway'
},
'bank_details': {
'account_name': 'Acme Inc',
'account_number': '9876543210',
'routing_number': '021000021',
'bank_name': 'Chase',
'account_category': 'Checking'
},
'company_name': 'Acme Inc'
}
response = requests.post(url, headers=headers, data=json.dumps(data))
result = response.json()
print(f"Bank Account ID: {result['bank_account']['id']}")Success Response
Status Code: 201 Created
{
"status": "success",
"message": "Bank account added successfully",
"bank_account": {
"id": "f3a1b2c4-1234-4abc-9def-1234567890ab",
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"entity_type": "PERSONAL",
"email": "john@example.com",
"address": {
"country_code": "US",
"state": "CA",
"city": "San Francisco",
"postcode": "94103",
"street_address": "123 Main St"
},
"bank_details": {
"account_name": "John Doe",
"account_number": "1234567890",
"routing_number": "111000025",
"bank_name": "Bank of America",
"account_category": "Checking"
},
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1990-01-15",
"created_at": "2026-04-19 14:30:00"
}
}Save the returned bank_account.id — you'll pass it as bank_account_id when creating a payout.
Error Responses
| Status | Error |
|---|---|
| 400 | "api_key is required" |
| 400 | "entity_type must be PERSONAL or COMPANY" |
| 400 | "email is required" / "Invalid email format" |
| 400 | "address.state, address.city, address.postcode and address.street_address are required" |
| 400 | "bank_details.account_name, account_number, routing_number and bank_name are required" |
| 400 | "first_name and last_name are required for PERSONAL accounts" |
| 400 | "company_name is required for COMPANY accounts" |
| 401 | "Invalid API Key" |
| 403 | "This vendor account has been banned." |
| 404 | "Customer not found" |
Payout to Customer Bank Account
Send an instant payout (Airwallex RTP / FedNow) from your vendor credit wallet to one of a customer's saved bank accounts.
Funds Path
Vendor credit_wallet_balance
→ Customer wallet
→ Airwallex bank push (RTP / FedNow)
→ Customer bank accountIf the Airwallex transfer fails, the funds are automatically reversed back to the vendor's credit_wallet_balance. The vendor must have sufficient credit_wallet_balance to cover the payout amount.
Endpoint
POST /api/v2/customer/{customer_id}/payoutRequest Headers
Content-Type: application/jsonRequest Body
{
"api_key": "your_api_key",
"bank_account_id": "f3a1b2c4-1234-4abc-9def-1234567890ab",
"amount": 100.00,
"currency": "USD"
}| Field | Required | Default | Description |
|---|---|---|---|
api_key | Yes | — | Your vendor API key |
bank_account_id | Yes | — | ID of a bank account belonging to this customer (see List Customer Bank Accounts) |
amount | Yes | — | Payout amount, must be > 0 |
currency | No | "USD" | Currency code (currently only "USD" is supported) |
Example Request
curl -X POST https://app.safepays.com/api/v2/customer/550e8400-e29b-41d4-a716-446655440000/payout \
-H "Content-Type: application/json" \
-d '{
"api_key": "your_api_key",
"bank_account_id": "f3a1b2c4-1234-4abc-9def-1234567890ab",
"amount": 100.00,
"currency": "USD"
}'const customerId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(
`https://app.safepays.com/api/v2/customer/${customerId}/payout`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: 'your_api_key',
bank_account_id: 'f3a1b2c4-1234-4abc-9def-1234567890ab',
amount: 100.00,
currency: 'USD'
})
}
);
const result = await response.json();
console.log('Payout status:', result.payout.status);import requests
import json
customer_id = '550e8400-e29b-41d4-a716-446655440000'
url = f'https://app.safepays.com/api/v2/customer/{customer_id}/payout'
headers = {'Content-Type': 'application/json'}
data = {
'api_key': 'your_api_key',
'bank_account_id': 'f3a1b2c4-1234-4abc-9def-1234567890ab',
'amount': 100.00,
'currency': 'USD'
}
response = requests.post(url, headers=headers, data=json.dumps(data))
result = response.json()
print(f"Payout status: {result['payout']['status']}")Success Response
Status Code: 200 OK
{
"status": "success",
"message": "Payout sent successfully",
"payout": {
"customer_id": "550e8400-e29b-41d4-a716-446655440000",
"bank_account_id": "f3a1b2c4-1234-4abc-9def-1234567890ab",
"amount": 100.00,
"currency": "USD",
"status": "PAID",
"airwallex_transfer_id": "trf_abc123xyz",
"airwallex_status": "DELIVERED"
}
}Response Fields
| Field | Type | Description |
|---|---|---|
customer_id | string | The customer that received the payout |
bank_account_id | string | The bank account the funds were sent to |
amount | number | Amount paid out |
currency | string | Currency code |
status | string | SafePays payout status (e.g. PAID) |
airwallex_transfer_id | string | Underlying Airwallex transfer ID for reconciliation |
airwallex_status | string | Airwallex transfer status (e.g. DELIVERED) |
Error Responses
| Status | Error |
|---|---|
| 400 | "api_key is required" |
| 400 | "bank_account_id is required" |
| 400 | "amount is required" / "Invalid amount format" |
| 400 | "amount must be greater than 0" |
| 400 | "Insufficient available balance" (vendor credit wallet has insufficient funds) |
| 400 | "Withdrawal exceeds your limit of $X per request" |
| 401 | "Invalid API Key" |
| 403 | "This vendor account has been banned." |
| 404 | "Customer not found" / "Bank account not found for this customer" |
| 502 | "Airwallex error: ..." (transfer rejected by Airwallex; funds auto-reversed to your credit wallet) |
A 502 response means Airwallex rejected the transfer. The payout amount has already been reversed back into your credit_wallet_balance, so it is safe to retry once the underlying issue (e.g. invalid routing number, closed account) is resolved.