> ## Documentation Index
> Fetch the complete documentation index at: https://docs.niobi.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Payout Best Practices

> Operational guidance, merchant wallet balance management, recipient validation rules, idempotency defense, and error retry strategies for disbursements.

## Overview

Building a reliable, automated disbursement pipeline requires strict wallet balance monitoring, rigorous recipient data validation, idempotency controls, and disciplined error handling. Follow these operational best practices to prevent failed transfers, avoid double-payouts, and maintain ledger integrity.

***

## 1. Merchant Wallet Balance Management & Pre-Check

Unlike collections, disbursements directly debit your Niobi merchant wallet in real time.

### Pre-Checking Wallet Balance via API

Before triggering automated or batch disbursement routines, query your current wallet balance using the [Get Account Balance API](/api-reference/get-account-balance) (`POST /api/v3/get-unified-balance`):

```json theme={null}
{
  "success": true,
  "message": "Balance fetched successfully.",
  "data": {
    "currency": "KES",
    "total_balance": 185943.4,
    "on_hold_balance": 0,
    "available_balance": 185843.4,
    "unsettled_balance": 100
  }
}
```

#### Key Balance Fields:

* **`available_balance` (Primary Indicator):** Always evaluate `available_balance` to determine if you have sufficient funds to cover the payout principal plus transaction fees. Do not rely on `total_balance`, as it includes funds that are currently locked or in-flight (`on_hold_balance` / `unsettled_balance`).
* **Why `available_balance` Contains Decimal Points:** You may notice fractional decimal values (e.g. `185843.4`) in your balance. These fractional values are the cumulative result of percentage-based transaction fee deductions across various payment corridors.

<Important>
  **Strict Integer Rule for `amount` (No Decimal Points):**\
  Even though your `available_balance` may show decimal points due to fee deductions, **all `amount` values submitted in API requests (both Collections and Payouts) MUST be whole integers** (e.g. `1000` for 1,000 KES/NGN, not `1000.50` or `1000.00`). Submitting decimal amounts will cause the request to fail validation.
</Important>

### Best Practices for Balance Operations:

* **Maintain Safety Buffers:** Set up automated alerts on your server when your `available_balance` approaches operational thresholds (e.g. less than 20% of estimated daily volume).
* **Handle Insufficient Balance Gracefully:** If an initiation returns `status_code: "000005"` with `"Insufficient merchant wallet balance"`, pause your payout queue, notify your treasury team to top up, and avoid flooding the endpoint with automated retries.

***

## 2. The Double-Verification Reconciliation Recipe for Payouts

For payroll disbursements, large vendor payments, or automated cross-border transfers, implement Niobi's recommended double-verification recipe: rely on the `payment_step: 2` callback as your primary signal, fall back to a status query if it hasn't arrived, and only close out the payout once that confirmed status is reconciled against your own internal payout records:

```mermaid theme={null}
flowchart TD
    A["1. Initiate Payout (POST /api/v4/niobi-unified-payments)"] --> B["Receive payment_step: 1 (Initiated)"]
    B --> C["Wait for Incoming Callback"]
    C --> D{"payment_step: 2 Callback Received?"}
    D -- Yes --> E["Step 1: Verify Inbound Signature"]
    D -- "No (after a while)" --> F["Call Status API (POST /api/v3/get-unified-transaction-status) with Spaced Intervals"]
    E --> G["Step 2: Acquire DB Lock on Reference"]
    F --> G
    G --> H{"Step 3: Triple-Point Match?<br/>1. status == 'success' & statusCode == '000000'<br/>2. amount & currency match payout order<br/>3. payment_reference matches"}
    H -- "Match Confirmed" --> I["Step 4: Atomic DB Update (Set status = 'SETTLED')"]
    H -- "Payout Failed / Returned" --> J["Refund Merchant Internal Ledger / Notify User"]
```

### Reconciliation Checklist:

1. **Inbound Signature Verification:** Always verify the incoming SHA-256 callback signature using your integration Secret Key (`senderKey`) before parsing the payload.
2. **Concurrency Lock & Idempotency Check:** Acquire a row-level database lock on `payment_reference` before processing balance updates. If both a callback and a status query arrive concurrently, only the first thread processes the reconciliation while the second safely exits.
3. **Triple-Point Data Match:** Confirm that all three attributes match your internal payout instruction:
   * **Status & Code:** Both `status: "success"` and `statusCode: "000000"` ([Success Code 000000](/errors/000000-success)).
   * **Amount & Currency:** The disbursed `amount` matches the intended transfer amount.
   * **Reference:** The `payment_reference` or `third_party_reference_1` matches your internal payout ID.
4. **Atomic Ledger Settlement:** Update your local transfer record to `SETTLED` inside a single atomic database transaction.

***

## 3. Recipient Phone & Account Validation Rules

Improper MSISDN or bank account formatting is the leading cause of instant disbursement rejections.

### Formatting Rules:

* **Digits Only:** Strip all spaces, plus signs (`+`), hyphens, parentheses, or symbols from phone numbers and bank accounts.
* **Phone Number Length:** Between 8 and 15 digits (including country code).
* **International Prefix without Plus:** Always prepend the destination country code, but omit the leading `+` or local trunk `0`.

| Country      | Channel                 | Correct Format           | Incorrect Format                   |
| :----------- | :---------------------- | :----------------------- | :--------------------------------- |
| **Kenya**    | M-Pesa / Airtel         | `254712345678`           | `+254 712 345 678` or `0712345678` |
| **Ghana**    | MTN / Vodafone / AT     | `233501234567`           | `+233 50 123 4567` or `0501234567` |
| **Uganda**   | MTN / Airtel            | `256770000000`           | `+256 77 000 0000`                 |
| **Tanzania** | Vodacom / Airtel / Tigo | `255750000000`           | `+255 75 000 0000`                 |
| **Cameroon** | MTN / Orange            | `237670000000`           | `+237 67 000 0000`                 |
| **Nigeria**  | NUBAN Bank Account      | `0123456789` (10 digits) | `012-345-6789`                     |

***

## 4. Idempotency & Reference Generation Best Practices

To prevent duplicate payouts caused by network retries or transient connection errors:

### Generating References on Your Side

Generating your own tracking references (`payment_reference` and `third_party_reference_1`) directly on your backend before dispatching requests is strongly recommended:

* **Format:** Use structured, easily traceable identifiers (such as `PAYOUT-2026-USER45-001` or a UUIDv4).
* **Safe Retries:** If an API call times out before receiving a response, resubmitting the request with the **same** reference guarantees that Niobi will not execute a duplicate transfer.
* **Audit & Status Queries:** Having your own reference stored locally lets you reconcile asynchronous callbacks (`payment_step: 2`) or query the [Get Transaction Status API](/transaction-status) using your own identifier.

### Enforcing Strict Uniqueness (`is_third_party_reference_1_unique`)

To have Niobi automatically reject duplicate disbursement submissions, pass the uniqueness flag in your request `params`:

```json theme={null}
{
  "is_third_party_reference_1_unique": 1,
  "is_third_party_reference_2_unique": 1
}
```

When set to `1` (or `true`), Niobi verifies that no prior transaction exists with that reference. If a duplicate reference is detected, Niobi immediately blocks the transfer and returns:

```json theme={null}
{
  "success": false,
  "message": "We apologize as we were not able to process your payment request. Please try again later.",
  "data": {
    "error": true,
    "message": "Duplicate third_party_reference_1 was found!"
  }
}
```

### Key Rules for Payout Uniqueness:

* **Assign Unique References per Payout:** Every distinct payout instruction must carry a unique `payment_reference` or `third_party_reference_1`.
* **Generate Unique Salt per Request:** Always generate a fresh random `salt` string for each API call to ensure cryptographic uniqueness and prevent replay attacks.
* **Avoid Blind Retries with New References:** If an HTTP request times out, never generate a new reference to retry the same payout. Re-send with the original reference or query status first.

***

## 5. Error Handling & Retry Policies

<AccordionGroup>
  <Accordion title="1. Handling 4xx Client Errors (Do Not Auto-Retry)">
    **Examples:** [`400 Bad Request`](/errors/client-errors), [`403 Request not verified`](/errors/authentication-errors), validation errors.\
    **Action:** Do not retry automatically. Check parameter validation, verify your signature generation algorithm, ensure required corridor fields (such as Cameroon sender details) are supplied, and correct the payload before resending. See [400 Client Errors](/errors/client-errors) for code breakdowns.
  </Accordion>

  <Accordion title="2. Handling 5xx Server Errors (Retry with Exponential Backoff)">
    **Examples:** `500 Internal Server Error`, `502 Bad Gateway`, `504 Gateway Timeout`.\
    **Action:** Implement exponential backoff (e.g. retry after 2s, 4s, 8s, 16s) up to a maximum of 3 to 5 attempts. If timeouts persist, check payout status using your reference before re-initiating.
  </Accordion>

  <Accordion title="3. Handling Failed Callbacks (Recipient Inactive / Reversal)">
    **Examples:** `status: "failed"`, `statusCode: "000005"`, `failureReason.failureCode: "DS-008"`.\
    **Action:** Funds are automatically refunded to your merchant wallet. Notify the recipient or prompt them to provide an active, registered phone number or bank account.
  </Accordion>
</AccordionGroup>

***

## Next Steps

Complete your integration and explore developer references:

<div className="next-steps-flow">
  <a href="/making-payments/basics" className="next-step-card">
    <div className="next-step-badge">Review</div>
    <h3>Disbursement Basics</h3>
    <p>Review payout fundamentals, parameter schemas, and payment step transitions.</p>
  </a>

  <div className="next-step-arrow">
    <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path d="M5 12h14M12 5l7 7-7 7" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  </div>

  <a href="/api-reference/get-transaction-status" className="next-step-card">
    <div className="next-step-badge">API Reference</div>
    <h3>Get Transaction Status API</h3>
    <p>Implementation guide for querying payout status and reconciliation.</p>
  </a>
</div>
