Bank Transfer API
Learn how to process bank transfers directly to bank accounts using . Transfers are processed instantly.
Overview
Bank Transfer allows you to move funds from your business balance to a bank account. Charges are calculated using your Wallet → Bank pricing: tiered flat charges per amount range, with optional business-specific overrides. You always pay transfer amount + charge; the recipient receives the transfer amount.
API vs Dashboard
- API – You send recipient bank details (
bank_name,bank_account_number, and optionallybank_account_name,bank_branch). The account is validated before the transfer; you can send to any supported bank. No verified business bank account is required. - Dashboard – From the Bank Transfer page you can send to your own account or to a different bank account (with validation).
How Bank Transfers Work
- You create a transfer with amount, description, recipient bank details, and optionally
wallet_source(API: account is validated before sending). - Charge is calculated from your Wallet → Bank pricing (tiered flat charges per amount range). Total deducted = amount + charge.
- Balance is deducted from the selected wallet (main or card) immediately before the provider call.
- Transfer is sent to the destination account.
- Status is set to
pending,processing,completed, orfailed. - You can poll
GET /bank-transfer/{reference}or use webhooks for the final result.
Wallet Selection
Every business has two wallets. Pass wallet_source to choose which wallet to debit:
| Value | Wallet | Description |
|---|---|---|
main (default) |
Main Wallet | Mobile money collections (MTN, Airtel) |
card |
Card Wallet | Card payment collections |
If omitted, wallet_source defaults to main. If a transfer fails, the refund goes back to the same wallet that was debited. See the Card Payments docs for how card wallet funds accumulate.
Wallet to Bank Pricing
Wallet → Bank (push to bank) transfers use tiered flat charges based on the transfer amount. For every transfer you pay amount + charge, and the recipient receives the transfer amount.
Default global tiers (UGX)
| Amount (UGX) | Charge (UGX) |
|---|---|
| 2,500 – 250,000 | 5,000 |
| 250,001 – 500,000 | 6,000 |
| 500,001 – 1,000,000 | 9,000 |
| 1,000,001 – 2,000,000 | 13,500 |
| 2,000,001 – 50,000,000 | 16,500 |
These are the standard tiers used for Wallet → Bank transfers.
Per-business overrides
- These charges apply to all businesses unless special pricing has been agreed.
- Where custom pricing is agreed for a business, those rates are used instead of the standard tiers.
- The correct charge is automatically selected based on the transfer amount.
- The minimum transfer amount is usually 2,500 UGX (or your agreed minimum).
API Base URL
All bank transfer API requests should be made to the following base URL:
https://wallet.wearemarz.com/api/v1
API Endpoints
/bank-transfer
Create bank transfer
/bank-transfer/services
Get bank transfer availability & settings
/bank-transfer/{reference}
Get bank transfer details
/bank-transfer/validate
Validate bank account details
/bank-transfer/banks
Get list of supported banks
Authentication
All bank transfer API requests require authentication using your API key. Include your API key in the request headers:
X-API-Key: your_api_key_here
Create Bank Transfer
Create a new bank transfer to any valid bank account. Send recipient bank_name and bank_account_number (and optionally bank_account_name, bank_branch). Subscription is handled internally. The recipient account is validated before the transfer; you do not need verified business bank details. The balance (transfer amount + charge) is deducted immediately and the transfer is sent. Status is processing or completed on success, or failed on error (balance is refunded on failure).
Validate before sending
We recommend calling POST /bank-transfer/validate first with bank_name and account_number to ensure the recipient account is valid and working before you create the transfer. This avoids failed transfers and refunds. The create-transfer endpoint also validates the account internally; validating first gives you early confirmation and the account holder name to display or store.
Request
https://wallet.wearemarz.com/api/v1/bank-transfer
Request Body
{
"amount": 100000,
"description": "Payment for services",
"bank_name": "Equity Bank",
"bank_account_number": "60001256421",
"bank_account_name": "John Doe",
"bank_branch": "Kampala",
"wallet_source": "main"
}
bank_account_name, bank_branch, and wallet_source are optional. If bank_account_name is omitted, it is fetched from the validate-account step. If wallet_source is omitted, it defaults to main. Use card to withdraw from the card wallet. Use bank names from /bank-transfer/banks (e.g. Equity Bank, Stanbic Bank).
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
amount |
number | Yes | Transfer amount in UGX (required). Minimum: your business minimum_transfer_amount from /bank-transfer/services. Recipient receives this amount; you are charged on top according to your Wallet → Bank pricing. |
description |
string | Yes | Description of the transfer (max 500 characters) |
bank_name |
string | Yes | Recipient bank name (e.g. Equity Bank, Stanbic Bank). Use names from /bank-transfer/banks. |
bank_account_number |
string | Yes | Recipient bank account number. Validated before the transfer. |
bank_account_name |
string | No | Recipient account name. If omitted, obtained from the validate-account step. |
bank_branch |
string | No | Recipient bank branch (optional) |
wallet_source |
string | No | Which wallet to debit: main (default) or card. Use card to withdraw card payment collections. If omitted, defaults to main. |
Response (Success) – HTTP 201
The reference is a UUID; use it with GET /bank-transfer/{reference} to check status. Possible status: processing, completed.
{
"status": "success",
"message": "Bank transfer is being processed. Reference: b0faa118-3e00-40ee-9513-67e371f9a32f. It may take a few minutes to complete.",
"data": {
"bank_transfer": {
"id": 1,
"reference": "b0faa118-3e00-40ee-9513-67e371f9a32f",
"transaction_uuid": "b0faa118-3e00-40ee-9513-67e371f9a32f",
"amount": {
"formatted": "100,000.00",
"raw": 100000,
"currency": "UGX"
},
"charge_amount": {
"formatted": "50,000.00",
"raw": 50000,
"currency": "UGX"
},
"total_amount": {
"formatted": "150,000.00",
"raw": 150000,
"currency": "UGX"
},
"description": "Payment for services",
"status": "processing",
"wallet_source": "main",
"bank_details": {
"bank_name": "Equity Bank",
"account_name": "John Doe",
"account_number": "60001256421",
"branch": "Kampala"
},
"balance": {
"current": "1,000,000.00",
"after_transaction": "850,000.00"
},
"provider": {
"transaction_id": "TXN-123456",
"status_code": "122",
"status_description": "Processing"
},
"created_at": "2026-02-20 16:14:46"
}
}
}
Get Bank Transfer Availability & Settings
Check if bank transfer is available for your business and retrieve charge and minimum amount settings. Subscription details are handled internally and are not exposed.
Request
https://wallet.wearemarz.com/api/v1/bank-transfer/services
Response (Success)
{
"status": "success",
"data": {
"bank_transfer_available": true,
"bank_transfer_settings": {
"amount_type": "fixed",
"charge_percentage": 3.5,
"minimum_transfer_amount": 2500,
"fixed_transfer_charge": 50000
},
"service": {
"name": "Bank Transfer Service",
"provider": "Banks"
}
},
"message": "Bank transfer is available."
}
Get Transfer Details
Retrieve a specific bank transfer by the reference returned from the create endpoint (UUID format). Use this to poll status. Possible status values: pending, processing, completed, failed.
Request
https://wallet.wearemarz.com/api/v1/bank-transfer/{reference}
Replace {reference} with the UUID from the create response (e.g. b0faa118-3e00-40ee-9513-67e371f9a32f).
Response (Success)
{
"status": "success",
"data": {
"bank_transfer_request": {
"id": 1,
"reference": "b0faa118-3e00-40ee-9513-67e371f9a32f",
"amount": { "formatted": "100,000.00", "raw": 100000, "currency": "UGX" },
"charge_amount": { "formatted": "50,000.00", "raw": 50000, "currency": "UGX" },
"total_amount": { "formatted": "150,000.00", "raw": 150000, "currency": "UGX" },
"description": "Payment for services",
"status": "processing",
"bank_details": {
"bank_name": "Equity Bank",
"account_name": "John Doe",
"account_number": "60001256421",
"branch": "Kampala"
},
"balance": { "current": "1,000,000.00", "after_transaction": "850,000.00" },
"service": { "id": 45, "name": "Bank Transfer Service", "provider": "Banks" },
"timeline": { "created_at": "2026-02-20 16:14:46", "approved_at": "2026-02-20 16:14:46", "rejected_at": null },
"provider": { "transaction_id": "TXN-123456", "status_code": "122", "status_description": "Processing" }
}
}
}
Validate Bank Account
Validate the recipient account before creating a transfer to ensure it is valid and working. Call this endpoint with bank_name and account_number to get the account holder name and confirm the account exists. Use it when building UIs (e.g. dashboard “Send to a different bank account”) or in your flow before calling create transfer. The create-transfer API also validates internally, but validating first gives you early confirmation and avoids failed transfers. No verified business bank is required.
Request
https://wallet.wearemarz.com/api/v1/bank-transfer/validate
Request Body
{
"bank_name": "Equity Bank",
"account_number": "60001256421"
}
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
bank_name |
string | Yes | Name of the bank (e.g., "Equity Bank", "Centenary Bank", "DFCU Bank") |
account_number |
string | Yes | Bank account number to validate |
Response (Success)
{
"status": "success",
"message": "SUCCESS",
"data": {
"account_number": "60001256421",
"bank_name": "Equity Bank",
"bank_code": "EQU",
"account_name": "JOHN DOE",
"network": "EQU",
"is_registered": "ACTIVE",
"valid": true,
"status_code": 0,
"status_description": "SUCCESS"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
account_number |
string | The validated account number |
bank_name |
string | The bank name provided in the request |
bank_code |
string | The bank code (e.g., "EQU", "CENT", "DFCU") |
account_name |
string | Account holder name as returned by the validation service (auto-filled from validation) |
network |
string | Bank network code (usually same as bank_code) |
is_registered |
string | Registration status ("ACTIVE" if fully registered, may be empty if not fully registered) |
valid |
boolean | True if account is valid and fully registered (status_code is 0 and is_registered is "ACTIVE") |
status_code |
integer | Status code (0 = success) |
status_description |
string | Status description (e.g., "SUCCESS") |
Response (Error)
{
"status": "error",
"message": "Bank account validation failed",
"error_code": "VALIDATION_FAILED",
"data": {
"account_number": "60001256421",
"bank_name": "Equity Bank",
"bank_code": "EQU",
"status_code": 30,
"status_description": "INVALID PHONE NUMBER"
}
}
Validation errors: BANK_NOT_SUPPORTED (400) – bank name not in supported list; VALIDATION_FAILED (400) – provider rejected the account; SERVICE_NOT_AVAILABLE (400) – no active bank transfer subscription.
Important Notes
- The
account_namefield is populated from the validation response and can be used to pre-fill account holder information - Use
/bank-transfer/banksfor the exact bank names/codes (e.g. Stanbic = STANBIC, Stanchart = STAN) to avoidUNSUPPORTED/UNKNOWN BANKfrom the provider - If
validisfalse, the account may still exist but might not be fully registered; you can still attempt a transfer after verifying with the account holder
Get Supported Banks
Retrieve a list of all supported banks for bank transfers and account validation. This endpoint returns bank codes, display names, and common aliases that can be used when validating accounts or creating transfer requests.
Request
https://wallet.wearemarz.com/api/v1/bank-transfer/banks
Response (Success)
{
"status": "success",
"message": "Supported banks retrieved successfully",
"data": {
"banks": [
{
"code": "EQU",
"name": "Equity Bank",
"aliases": ["Equity Bank", "Equity", "EQU"]
},
{
"code": "CENT",
"name": "Centenary Bank",
"aliases": ["Centenary Bank", "Centenary", "CENT"]
},
{
"code": "DFCU",
"name": "DFCU Bank",
"aliases": ["DFCU", "DFCU Bank"]
},
{
"code": "STANBIC",
"name": "Stanbic Bank",
"aliases": ["Stanbic Bank", "Stanbic", "STANBIC"]
},
{
"code": "STAN",
"name": "Stanchart Bank",
"aliases": ["Stanchart Bank", "Stanchart", "Standard Chartered", "STAN"]
},
{
"code": "ABSA",
"name": "ABSA Bank",
"aliases": ["ABSA Bank", "ABSA"]
}
],
"count": 25
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
banks |
array | Array of supported bank objects |
count |
integer | Total number of supported banks |
Bank Object Structure
| Field | Type | Description |
|---|---|---|
code |
string | Bank code identifier (e.g., "EQU", "CENT", "DFCU") |
name |
string | Display name of the bank (e.g., "Equity Bank", "Centenary Bank") |
aliases |
array | List of accepted bank name variations that can be used in validation requests |
Stanbic vs Stanchart
Stanbic Bank (code STANBIC) and Stanchart Bank / Standard Chartered (code STAN) are different banks. Use the exact bank name from this list when validating or transferring to avoid provider errors.
Usage Tips
- Use any of the bank names from the
aliasesarray when calling the/bank-transfer/validateendpoint - The
codefield is a unique identifier for each bank that can be used for reference or filtering - Bank names are case-insensitive, so "Equity Bank", "equity bank", or "EQUITY BANK" will all work
- Always use
/bank-transfer/banksto get the current list; bank codes must match exactly for the provider to accept the transfer
Transfer Statuses
When you create or query a transfer, status can be:
pending– Created, not yet sent to providerprocessing– Sent to provider; completion pendingcompleted– Successfully completedfailed– Provider or system error; balance is refunded on create failure
Prerequisites
For the API: provide recipient bank details (bank_name, bank_account_number) in each request. The account is validated before the transfer; no verified business bank account is required.
An active bank transfer service (subscription is handled internally)
Sufficient balance in the selected wallet (transfer amount + charge). Use wallet_source = main or card.
Account must not be frozen. Only one transfer can be processing or pending at a time.
Best Practices
1. Check bank transfer availability and settings
Use GET /bank-transfer/services to see if bank transfer is available and to get bank_transfer_settings (minimum transfer amount and pricing details).
2. Verify Balance Before Requesting
Ensure you have sufficient balance in the selected wallet: you pay transfer amount plus charge; the recipient receives the transfer amount. Check available_balance (main wallet) or card_balance (card wallet) from GET /balance.
3. Handle Webhooks
Set up webhooks to receive real-time notifications when your bank transfers are processed, completed, or failed.
4. Store Reference Numbers
Save the reference number returned in the response. Use it to check the status of your transfer later.
5. Check Transfer Status
Transfers can have statuses: "processing" (being processed), "completed" (successful), or "failed" (error occurred). Use the reference to check status via the GET endpoint.
6. Validate the bank account first (recommended)
Validate before sending: Call POST /bank-transfer/validate with bank_name and account_number before creating a transfer. This ensures the recipient account is valid and working, avoids failed transfers and refunds, and returns the account holder name. Use bank names from /bank-transfer/banks (e.g. Stanbic = STANBIC, Stanchart = STAN).
Debugging & Logging
Bank transfer requests and responses are logged for troubleshooting. Check storage/logs/laravel.log and storage/logs/bank-YYYY-MM-DD.log for request/response entries.
Error Responses
Insufficient Balance
{
"status": "error",
"message": "Insufficient main wallet balance. Available: UGX 50,000, Required: UGX 103,500...",
"error_code": "INSUFFICIENT_BALANCE",
"data": {
"wallet_source": "main",
"current_balance": 50000,
"required_amount": 103500,
"transfer_amount": 100000,
"recipient_amount": 100000,
"charge_amount": 3500
}
}
Business Account Frozen
{
"status": "error",
"message": "Your business account is currently frozen...",
"error_code": "ACCOUNT_FROZEN"
}
Service Not Subscribed / Invalid Bank Account
403: SERVICE_NOT_SUBSCRIBED – No active bank transfer subscription. 400: BANK_CODE_NOT_FOUND – Bank name not supported. INVALID_BANK_ACCOUNT – Recipient account could not be validated (check account number and bank).
{
"status": "error",
"message": "Account could not be validated. Please check the account number and bank.",
"error_code": "INVALID_BANK_ACCOUNT"
}
Validation Error / Invalid Subscription
422: VALIDATION_ERROR – Invalid or missing fields (see errors). INVALID_SERVICE_SUBSCRIPTION – Subscription invalid or not active.
{
"status": "error",
"message": "Please check your input and try again.",
"error_code": "VALIDATION_ERROR",
"errors": { "amount": ["Transfer amount must be at least 2500 UGX."] }
}
Request Not Found
{
"status": "error",
"message": "Bank transfer request not found.",
"error_code": "REQUEST_NOT_FOUND"
}
API Connection Failed
500: API_CONNECTION_FAILED – Provider request failed; balance is refunded.
{
"status": "error",
"message": "Unable to process bank transfer at this time. Your balance has been refunded...",
"error_code": "API_CONNECTION_FAILED"
}
Processing Transfer Exists
{
"status": "error",
"message": "You already have a bank transfer being processed. Please wait for it to complete before submitting a new transfer.",
"error_code": "PROCESSING_TRANSFER_EXISTS",
"data": {
"processing_transfer": {
"reference": "BTR-1771595000-7772",
"amount": "100,000.00",
"status": "processing",
"created_at": "2026-02-20 16:14:46"
}
}
}
Transfer Failed
{
"status": "error",
"message": "Bank transfer could not be completed. Your balance has been refunded. Please try again later or contact support if the issue persists.",
"error_code": "TRANSFER_FAILED",
"data": {
"status_code": "30",
"status_description": "INVALID ACCOUNT"
}
}
Unsupported or Unknown Bank
The provider may return UNSUPPORTED/UNKNOWN BANK if the bank code is not recognised. Ensure you use a bank name/code from /bank-transfer/banks. Stanbic Bank uses code STANBIC; Stanchart (Standard Chartered) uses STAN.
{
"status": "error",
"message": "UNSUPPORTED/UNKNOWN BANK",
"error_code": "TRANSFER_FAILED",
"data": { "status_description": "UNSUPPORTED/UNKNOWN BANK" }
}
Invalid Account (From/To)
Errors such as Invalid Account (From/To) (SCB Code: 78) usually mean the account number does not match the bank (e.g. wrong bank selected for the account). Verify the account with /bank-transfer/validate using the correct bank name from /bank-transfer/banks.
{
"status": "error",
"message": "Invalid Account (From/To) (SCB Code: 78)",
"error_code": "TRANSFER_FAILED",
"data": { "status_description": "Invalid Account (From/To) (SCB Code: 78)" }
}