Payouts (Outflow)
Payouts let you send money from your Capera balance to external accounts. Capera supports two payout channels:
| Channel | Destination | Endpoint prefix |
|---|---|---|
| NGN Bank Transfer | Any Nigerian bank account | /v1/transfers |
| Mobile Money Transfer | Mobile wallet (Ghana, Kenya, Uganda, and more) | /v1/momo/transfer |
NGN Bank Transfers
Send NGN directly to any Nigerian bank account. The flow is: get banks → resolve account → initiate → track status.
Step 1: Get available banks
Returns all Nigerian banks supported for transfers. No authentication required. Cache this list — it changes infrequently.
GET /v1/banksResponse
[
{ "id": "bank_001", "name": "First Bank of Nigeria", "slug": "first-bank" },
{ "id": "bank_002", "name": "Guaranty Trust Bank", "slug": "gtb" },
{ "id": "bank_003", "name": "United Bank for Africa", "slug": "uba" },
{ "id": "bank_004", "name": "Access Bank", "slug": "access-bank" },
{ "id": "bank_005", "name": "Zenith Bank", "slug": "zenith-bank" }
]Use the slug value when specifying the destination bank.
Step 2: Resolve the destination account
Verify the account number is valid and retrieve the account holder's name. Always do this before initiating a transfer — it prevents sending to the wrong account.
GET /v1/bank/resolve?accountNumber=1234567890&bankSlug=first-bank
Authorization: Bearer <api-key>Query parameters
| Parameter | Required | Description |
|---|---|---|
accountNumber | Yes | 10-digit bank account number |
bankSlug | Yes | Bank slug from the Get Banks response |
Response
{
"accountName": "Amara Okafor"
}Show the accountName to the user before they confirm the payout. This is the most effective way to prevent wrong-account transfers.
Step 3: Initiate the transfer
POST /v1/transfers/initiate
Authorization: Bearer <api-key>
Content-Type: application/json{
"reference": "TRF-2024-001",
"amount": 500000,
"bankSlug": "first-bank",
"accountNumber": "1234567890",
"accountName": "Amara Okafor",
"narration": "Salary payment - January 2024"
}Request fields
| Field | Type | Required | Description |
|---|---|---|---|
reference | string | Yes | Your unique identifier for this transfer — used for idempotency |
amount | integer | Yes | Amount in kobo. ₦1 = 100 kobo. Must be greater than 0. |
bankSlug | string | Yes | Bank slug from the Get Banks response |
accountNumber | string | Yes | Destination 10-digit account number |
accountName | string | Yes | Account holder name — should match the resolve response |
narration | string | No | Description shown on the recipient's bank statement (max 100 chars) |
Use a unique
referencefor every transfer. Retrying with the same reference returns the original result — no duplicate is created. See Idempotency.
Response
{
"status": "PENDING"
}The transfer is accepted and queued. Processing is asynchronous.
Step 4: Check transfer status
GET /v1/transfers/{reference}
Authorization: Bearer <api-key>Response
{
"reference": "TRF-2024-001",
"status": "SUCCESS",
"amount": 500000,
"fee": 50
}Transfer statuses
| Status | Meaning |
|---|---|
PENDING | Queued, not yet sent to the bank |
PROCESSING | Submitted to the bank, awaiting confirmation |
SUCCESS | Funds delivered to the destination account |
FAILED | Transfer failed — funds are returned to your balance |
CANCELLED | Transfer was cancelled before processing |
Webhook events
Subscribe to these events to avoid polling:
| Event | Trigger |
|---|---|
transfer.initiated | Transfer was accepted |
transfer.pending | Transfer is being processed by the bank |
transfer.success | Transfer completed successfully |
transfer.failed | Transfer failed |
transfer.success payload
{
"id": "evt_abc123",
"event": "transfer.success",
"timestamp": "2024-01-15T12:00:00Z",
"data": {
"id": "trf_xyz789",
"reference": "TRF-2024-001",
"amount": 500000,
"currency": "NGN",
"status": "SUCCESSFUL",
"destination": "Amara Okafor",
"accountNumber": "1234567890",
"bankName": "First Bank of Nigeria",
"narration": "Salary payment - January 2024",
"sessionId": "000001240115...",
"createdAt": "2024-01-15T11:55:00Z",
"updatedAt": "2024-01-15T12:00:00Z"
}
}Mobile Money Transfers
Send funds directly to a mobile wallet. The flow mirrors NGN transfers: list operators → resolve account → initiate → track status.
Step 1: List MoMo operators
GET /v1/momo/operators/{country_code}No authentication required. Use the ISO 3166-1 alpha-2 country code for the destination country.
GET /v1/momo/operators/GH?type=transferResponse
{
"success": true,
"data": [
{ "operator": "MTN Ghana", "operatorCode": "MTN_GH" },
{ "operator": "Vodafone Ghana", "operatorCode": "VOD_GH" },
{ "operator": "AirtelTigo Ghana", "operatorCode": "ATL_GH" }
]
}Step 2: Resolve the MoMo account (recommended)
Verify the phone number before sending.
GET /v1/momo/resolve-account
Authorization: Bearer <api-key>
Content-Type: application/json{
"operatorCode": "MTN_GH",
"phoneNumber": "+233241234567"
}Response
{
"success": true,
"data": {
"firstName": "Kwame",
"lastName": "Mensah",
"operatorCode": "MTN_GH"
}
}Step 3: Initiate the MoMo transfer
POST /v1/momo/transfer
Authorization: Bearer <api-key>
Content-Type: application/json{
"reference": "MOMO-TRF-001",
"amount": 5000,
"operatorCode": "MTN_GH",
"phoneNumber": "+233241234567",
"narration": "Vendor payment - January 2024",
"counterparty": {
"firstName": "Kwame",
"lastName": "Mensah"
}
}Request fields
| Field | Type | Required | Description |
|---|---|---|---|
reference | string | Yes | Your unique identifier for this transfer |
amount | integer | Yes | Amount in the currency's minor unit. Must be greater than 0. |
operatorCode | string | Yes | Operator code from the List Operators response |
phoneNumber | string | Yes | Recipient's phone number in E.164 format |
narration | string | No | Transfer description |
counterparty.firstName | string | Yes | Recipient's first name |
counterparty.lastName | string | Yes | Recipient's last name |
Response
{
"success": true,
"data": {
"reference": "MOMO-TRF-001",
"status": "PENDING",
"amount": 5000,
"fee": 0
}
}Step 4: Check MoMo transfer status
GET /v1/momo/transfer/{reference}
Authorization: Bearer <api-key>Response
{
"success": true,
"data": {
"reference": "MOMO-TRF-001",
"status": "SUCCESS",
"amount": 5000,
"fee": 0
}
}Error handling
| Error | Cause | Resolution |
|---|---|---|
Bank not found | Invalid bankSlug | Use a slug from GET /v1/banks |
Account not found | Account number does not exist at the given bank | Verify the account number |
Amount must be greater than 0 | Zero or negative amount | Use a positive integer |
Reference already exists | Duplicate reference | Check the transfer status — it may already exist |
Session not found | Missing or invalid API key | Check your Authorization header |
Best practices
Always resolve before sending — Run GET /v1/bank/resolve or GET /v1/momo/resolve-account before every transfer. Account name verification is the most reliable way to prevent sending to the wrong destination.
Cache the bank list — Banks change infrequently. Cache GET /v1/banks for at least 24 hours to avoid redundant calls.
Use webhooks over polling — Subscribe to transfer.success and transfer.failed instead of polling GET /v1/transfers/:reference. This reduces latency and API load.
Handle FAILED gracefully — When a transfer fails, the amount is returned to your balance. Notify the initiator and allow them to retry with a new reference.
What's next
- Collections — receive payments via virtual accounts and MoMo
- Deposits — query your incoming deposit history
- Idempotency — safely retry failed requests
Updated 4 months ago