Bank Transfer API
Learn how to process bank transfers directly to bank accounts using Marz Pay. Transfers are processed instantly.
Overview
Bank Transfer allows you to move funds from your business balance to a bank account. Charges are calculated from your business settings: fixed (a set charge per transfer) or percentage (a percentage of the transfer amount). 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, and recipient bank details (API: account is validated before sending).
- Charge is calculated from your amount type (fixed or percentage). Total deducted = amount + charge.
- Balance is deducted 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.
Amount Type Settings
Your business can configure how bank transfer charges are calculated. You can choose between "fixed" (default) or "percentage" amount types. This setting is managed in your Bank Settings page.
Fixed Amount Type (Default)
When using fixed amount type, a fixed charge amount is applied per transaction. You pay transfer amount plus the fixed charge; the recipient receives the transfer amount.
Example: Transfer 100,000 UGX with fixed charge of 50,000 UGX
• Transfer amount: 100,000 UGX
• Fixed charge: 50,000 UGX
• Total deducted: 150,000 UGX
• Recipient receives: 100,000 UGX
Percentage Amount Type
When using percentage type, the charge is calculated as a percentage of the transfer amount. You specify the transfer amount, and the charge is calculated and added on top.
Example: Transfer 100,000 UGX with 2% charge
• Transfer amount: 100,000 UGX
• Percentage charge (2%): 2,000 UGX
• Total deducted: 102,000 UGX
• Recipient receives: 100,000 UGX
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"
}
bank_account_name and bank_branch are optional. If bank_account_name is omitted, it is fetched from the validate-account step. 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 (fixed or percentage). |
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) |
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",
"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": 1000,
"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 (transfer amount + charge). Charge comes from your settings (fixed or percentage).
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 (amount type, charge, minimum transfer amount).
2. Verify Balance Before Requesting
Ensure you have sufficient balance: you pay transfer amount plus charge; the recipient receives the transfer amount.
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 balance. Available: UGX 50,000, Required: UGX 103,500...",
"error_code": "INSUFFICIENT_BALANCE",
"data": {
"current_balance": 50000,
"required_amount": 103500,
"transfer_amount": 100000,
"recipient_amount": 100000,
"charge_amount": 3500,
"charge_percentage": 3.5
}
}
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 1000 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)" }
}