Payouts API
Payouts API
Send funds to bank accounts via EFT (Electronic Funds Transfer).
Payout Flow
- Check balance - Ensure sufficient funds available
- Create payout - Call
POST /api/v1/payoutswith recipient details - Await approval - Operator reviews and approves the payout
- Processing - Funds are transferred to recipient
- Receive webhook - Get notified of final status
Balance Management
Before creating payouts, check your available balance.
Check Balance
Endpoint: GET /api/v1/payouts/balance
curl -X GET "https://api.qrpay2.com/api/v1/payouts/balance" \ -H "X-API-Key: $API_KEY"Response (200 OK):
{ "status": "success", "data": { "merchant_id": "01932e5d-7f8a-7890-b123-456789abcdef", "balance_cents": 500000, "reserved_cents": 100000, "available_cents": 400000, "currency": "ZAR" }}Balance Fields:
balance_cents- Total balance for payoutsreserved_cents- Funds reserved for pending/approved payoutsavailable_cents- Balance available for new payouts
Payout Methods
| Method | Code | Description | Processing Time |
|---|---|---|---|
| EFT | eft | Standard bank transfer to SA accounts | 1-3 business days |
Create Payout
Endpoint: POST /api/v1/payouts
Request:
{ "amount_cents": 50000, "currency": "ZAR", "payout_method": "eft", "payment_details": { "account_number": "1234567890", "branch_code": "123456", "account_holder": "John Doe", "bank_name": "First National Bank" }, "merchant_reference": "PAYOUT-2025-001", "description": "Customer refund"}Validation Rules:
amount_cents: Required, must be greater than 0currency: Required, must beZARpayout_method: Required, must beeftbranch_code: Exactly 6 digitsmerchant_reference: Optional, max 100 chars
Response (201 Created):
{ "status": "success", "data": { "id": "01932e5d-7f8a-7890-b123-456789abcdef", "merchant_id": "01932e5d-7000-7890-b123-456789abcdef", "amount_cents": 50000, "currency": "ZAR", "status": "pending", "payout_method": "eft", "merchant_reference": "PAYOUT-2025-001", "created_at": "2025-01-07T12:00:00Z" }}What Happens Next
- Balance is immediately reserved
- Payout enters
pendingstatus - Operator reviews and approves/rejects
- Final status triggers webhook notification
Payout Lifecycle
States
| State | Description | Terminal? | Balance Impact |
|---|---|---|---|
pending | Awaiting operator approval | No | Reserved |
approved | Approved, ready for transfer | No | Reserved |
processing | Transfer initiated | No | Reserved |
completed | Transfer successful | Yes | Deducted |
failed | Transfer failed | Yes | Released |
rejected | Rejected by operator | Yes | Released |
State Transitions
pending → approved → processing → completed ↓ ↓ ↓rejected rejected failedGet Payout Status
Endpoint: GET /api/v1/payouts/{payout_id}
curl -X GET "https://api.qrpay2.com/api/v1/payouts/$PAYOUT_ID" \ -H "X-API-Key: $API_KEY"Response (200 OK):
{ "status": "success", "data": { "id": "01932e5d-7f8a-7890-b123-456789abcdef", "amount_cents": 50000, "currency": "ZAR", "status": "completed", "payout_method": "eft", "merchant_reference": "PAYOUT-2025-001", "external_transfer_id": "TXN-ABC123XYZ789", "created_at": "2025-01-07T12:00:00Z", "completed_at": "2025-01-07T14:30:00Z" }}List Payouts
Endpoint: GET /api/v1/payouts
Query Parameters:
limit: Number of results (default: 50, max: 100)offset: Pagination offset (default: 0)status: Filter by status (optional)
curl -X GET "https://api.qrpay2.com/api/v1/payouts?limit=10&status=completed" \ -H "X-API-Key: $API_KEY"Code Examples
cURL
#!/bin/bashAPI_BASE="https://api.qrpay2.com"API_KEY="sec1-prd-YOUR_KEY..."
curl -X POST "$API_BASE/api/v1/payouts" \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount_cents": 50000, "currency": "ZAR", "payout_method": "eft", "payment_details": { "account_number": "1234567890", "branch_code": "123456", "account_holder": "John Doe", "bank_name": "First National Bank" }, "merchant_reference": "PAYOUT-2025-001" }'Python
import requestsimport os
API_BASE = "https://api.qrpay2.com"API_KEY = os.environ["QRPAY_API_KEY"]
def create_payout(amount_cents: int, account_details: dict, reference: str) -> dict: response = requests.post( f"{API_BASE}/api/v1/payouts", headers={ "X-API-Key": API_KEY, "Content-Type": "application/json", }, json={ "amount_cents": amount_cents, "currency": "ZAR", "payout_method": "eft", "payment_details": account_details, "merchant_reference": reference, }, )
if response.status_code == 201: return response.json()["data"] elif response.status_code == 400: error = response.json()["error"] if error["code"] == "INSUFFICIENT_BALANCE": raise Exception("Insufficient balance for payout") raise Exception(f"Validation error: {error['message']}") else: response.raise_for_status()
# Usagepayout = create_payout( amount_cents=50000, account_details={ "account_number": "1234567890", "branch_code": "123456", "account_holder": "John Doe", "bank_name": "First National Bank", }, reference="PAYOUT-2025-001",)print(f"Payout created: {payout['id']}")Error Responses
| HTTP Code | Error Code | Description |
|---|---|---|
| 400 | VALIDATION_FAILED | Invalid request parameters |
| 400 | INSUFFICIENT_BALANCE | Not enough available balance |
| 400 | INVALID_PAYMENT_DETAILS | Bank details validation failed |
| 404 | PAYOUT_NOT_FOUND | Payout not found |
| 409 | CONFLICT | Duplicate merchant_reference |