Skip to content

Payouts API

Payouts API

Send funds to bank accounts via EFT (Electronic Funds Transfer).

Payout Flow

  1. Check balance - Ensure sufficient funds available
  2. Create payout - Call POST /api/v1/payouts with recipient details
  3. Await approval - Operator reviews and approves the payout
  4. Processing - Funds are transferred to recipient
  5. Receive webhook - Get notified of final status

Balance Management

Before creating payouts, check your available balance.

Check Balance

Endpoint: GET /api/v1/payouts/balance

Terminal window
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 payouts
  • reserved_cents - Funds reserved for pending/approved payouts
  • available_cents - Balance available for new payouts

Payout Methods

MethodCodeDescriptionProcessing Time
EFTeftStandard bank transfer to SA accounts1-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 0
  • currency: Required, must be ZAR
  • payout_method: Required, must be eft
  • branch_code: Exactly 6 digits
  • merchant_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

  1. Balance is immediately reserved
  2. Payout enters pending status
  3. Operator reviews and approves/rejects
  4. Final status triggers webhook notification

Payout Lifecycle

States

StateDescriptionTerminal?Balance Impact
pendingAwaiting operator approvalNoReserved
approvedApproved, ready for transferNoReserved
processingTransfer initiatedNoReserved
completedTransfer successfulYesDeducted
failedTransfer failedYesReleased
rejectedRejected by operatorYesReleased

State Transitions

pending → approved → processing → completed
↓ ↓ ↓
rejected rejected failed

Get Payout Status

Endpoint: GET /api/v1/payouts/{payout_id}

Terminal window
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)
Terminal window
curl -X GET "https://api.qrpay2.com/api/v1/payouts?limit=10&status=completed" \
-H "X-API-Key: $API_KEY"

Code Examples

cURL

#!/bin/bash
API_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 requests
import 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()
# Usage
payout = 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 CodeError CodeDescription
400VALIDATION_FAILEDInvalid request parameters
400INSUFFICIENT_BALANCENot enough available balance
400INVALID_PAYMENT_DETAILSBank details validation failed
404PAYOUT_NOT_FOUNDPayout not found
409CONFLICTDuplicate merchant_reference