> ## 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.

# Disbursement Basics

> Core fundamentals of making disbursements across Africa: payment steps, request parameters, synchronous dispatch responses, and asynchronous terminal callbacks.

## Overview

Niobi's **Unified Payments API** allows your application to disburse funds to recipients across Africa through a single standardized integration. Whether disbursing via Mobile Money (M-Pesa, MTN, Airtel, Orange, Wave), Bank Transfers, Business Paybills, or Till Numbers, you send a signed request to the unified payments endpoint:

**Endpoint:** [`POST /api/v4/niobi-unified-payments`](/api-reference/disbursement/unified-disbursement)

Disbursements follow an asynchronous lifecycle:

```mermaid theme={null}
%%{init: {'themeVariables': {'fontSize': '23px'}, 'sequence': {'width': 220, 'height': 95, 'actorMargin': 80, 'messageMargin': 60, 'boxMargin': 18, 'noteMargin': 18}}}%%
sequenceDiagram
    autonumber
    participant Merchant as Your Backend Server
    participant Niobi as Niobi Unified Payments API
    participant TelcoBank as Recipient Telco / Banking Switch
    participant Recipient as Recipient (Payee)

    Note over Merchant: 1. Sign request envelope (SHA-256)<br/>2. Send POST /api/v4/niobi-unified-payments
    Merchant->>Niobi: POST /api/v4/niobi-unified-payments
    Note over Niobi: Validate signature, params & check merchant wallet balance
    Niobi-->>Merchant: Synchronous Response (On success: payment_step: 1, status: success, statusCode: 000000)
    Note over Merchant: Payout accepted & dispatched upstream
    Niobi->>TelcoBank: Submit payout to network switch
    TelcoBank->>Recipient: Credit recipient wallet / bank account
    TelcoBank->>Niobi: Report settlement confirmation
    alt Payout Succeeded
        Niobi->>Merchant: Webhook (payment_step: 2, status: "success", statusCode: "000000")
        Note over Merchant: Final clearance: Mark payout completed & update ledger
    else Payout Failed / Rejected
        Niobi->>Merchant: Webhook (payment_step: 2, status: "failed", statusCode: "000005")
        Note over Merchant: Payout failed: Mark failed in ledger & notify operations
    end
```

<div className="pstep-flow">
  <div className="pstep-card pstep-card--initial">
    <div className="pstep-card-header">
      <span className="pstep-badge pstep-badge--initial">Step 1</span>
      <span className="pstep-label">Request Initiation</span>
    </div>

    <code className="pstep-code">payment\_step: 1</code>

    <ol className="pstep-substeps">
      <li>Your backend signs the request envelope (SHA-256) and sends <code>POST /api/v4/niobi-unified-payments</code>.</li>
      <li>Niobi validates the signature and params, and checks your merchant wallet balance.</li>
      <li>Niobi returns the initial HTTP response to the request when it's successful, confirming the payout was accepted and dispatched upstream.</li>
    </ol>
  </div>

  <span className="pstep-arrow">→</span>

  <div className="pstep-card pstep-card--terminal">
    <div className="pstep-card-header">
      <span className="pstep-badge pstep-badge--terminal">Step 2</span>
      <span className="pstep-label">Terminal Outcome</span>
    </div>

    <code className="pstep-code">payment\_step: 2</code>

    <ol className="pstep-substeps">
      <li>Niobi submits the payout to the recipient's telco or banking switch.</li>
      <li>The switch credits the recipient's wallet or bank account, then reports settlement back to Niobi.</li>
      <li>Niobi sends an asynchronous callback to your <code>client\_callback\_url</code> with the final status.</li>
    </ol>
  </div>
</div>

***

## API Environments & Base URLs

Niobi provides separate environments for sandbox testing and live disbursement processing:

| Environment    | Base URL                         | Purpose                                                                 |
| :------------- | :------------------------------- | :---------------------------------------------------------------------- |
| **Sandbox**    | `https://sandbox.users.niobi.co` | Integration testing, mock disbursements, and simulator accounts.        |
| **Production** | `https://users.niobi.co`         | Live disbursements, real wallet balances, and actual recipient credits. |

All payout endpoints referenced throughout this documentation are relative to your chosen environment's base URL.

***

## Understanding Payment Steps (`payment_step`)

Like collections, disbursements operate on a two-step lifecycle:

<div className="pstep-flow">
  <div className="pstep-card pstep-card--initial">
    <div className="pstep-card-header">
      <span className="pstep-badge pstep-badge--initial">Step 1</span>
      <span className="pstep-label">Request Initiation</span>
    </div>

    <code className="pstep-code">payment\_step: 1</code>
    <p className="pstep-desc">Initial HTTP response to the request when it's successful, confirming the payout was validated and dispatched to the destination telco/bank switch.</p>
  </div>

  <span className="pstep-arrow">→</span>

  <div className="pstep-card pstep-card--terminal">
    <div className="pstep-card-header">
      <span className="pstep-badge pstep-badge--terminal">Step 2</span>
      <span className="pstep-label">Terminal Outcome</span>
    </div>

    <code className="pstep-code">payment\_step: 2</code>
    <p className="pstep-desc">Asynchronous callback delivering the final settlement outcome (success or failed).</p>
  </div>
</div>

### 1. `payment_step: 1` (Initial API Response)

* **Delivery:** Synchronous HTTP response returned immediately when calling `POST /api/v4/niobi-unified-payments`.
* **Meaning:** The request passed signature validation, parameter formatting was accepted, merchant wallet balance was confirmed, and the disbursement instruction was dispatched to the destination telco or banking switch.
* **Important:** This confirms that the transfer has been initiated, **not that funds have settled in the recipient's wallet or account**.

<Info>
  **Absence of `payment_step` on Initiation Failures:**\
  If a disbursement request fails during initial submission (for example, due to invalid recipient details, unsupported corridor, insufficient merchant wallet balance, or immediate processor rejection returning `status_code: "000005"`), the `"payment_step"` field is **not present** in the response. Your backend must inspect `status_code` (`"000000"` vs `"000005"`) rather than assuming `payment_step` is always returned.
</Info>

### 2. `payment_step: 2` (Terminal Callback)

* **Delivery:** Asynchronous callback POST request sent to the `client_callback_url` passed under `params.client_callback_url` in your request.
* **Meaning:** The transaction has reached its **final terminal state**. Funds have either been credited to the recipient or the payout was rejected/refunded by the banking/telco switch.
* **Action:** When `"payment_step": 2` is present in the `params` object of the callback, update your internal ledger and record the outcome based on `status: "success"` (`status_code: "000000"`) or `status: "failed"` (`status_code: "000005"`). Full details are covered in [Payout Callbacks](/making-payments/callbacks).

<Warning>
  **Rules of Transaction Settlement (Disbursement Lifecycle):**

  * **`payment_step: 1` = Payout Dispatched (Funds In Transit)**\
    Confirms that Niobi validated your signature, confirmed sufficient merchant wallet balance, and dispatched the payout instruction to the destination telco/bank switch.\
    **CRITICAL RULE:** Funds are in transit across banking/mobile money networks. **Do NOT treat the transfer as settled or cleared at this stage.**

  * **`payment_step: 2` = Terminal Outcome (Delivered via Webhook Callback)**\
    Delivered asynchronously to your `client_callback_url`. Inspect both `status` and `statusCode` (or `status_code`):
    * **Success (`status: "success"`, `statusCode: "000000"`):** The recipient's bank account or mobile wallet has officially confirmed receipt of the funds. **Update your ledger and notify the recipient.**
    * **Failed (`status: "failed"`, `statusCode: "000005"`):** The transfer failed or was rejected by the switch (e.g. invalid account number, inactive wallet, bank network offline). Niobi automatically refunds/unlocks the held wallet balance. **Mark the payout as failed in your ledger.**

  * **Missing or Delayed Webhook?**\
    If a callback hasn't arrived after a reasonable wait, query the [Get Transaction Status API](/transaction-status) (`POST /api/v3/get-unified-transaction-status`) to reconcile in-doubt payouts before re-attempting or failing a transfer.
</Warning>

***

## Request Parameters

The unified payments endpoint accepts standard parameters wrapped in the signed envelope:

### Root Envelope Fields

| Field           | Type      | Required | Description                                                           |
| :-------------- | :-------- | :------: | :-------------------------------------------------------------------- |
| **`client_id`** | `string`  |  **Yes** | Your integration Client ID from the Niobi Dashboard.                  |
| **`sender`**    | `string`  |  **Yes** | Your exact integration name as registered in the dashboard.           |
| **`timestamp`** | `integer` |  **Yes** | The exact UNIX timestamp (in seconds) used during signature creation. |
| **`salt`**      | `string`  |  **Yes** | A unique random string generated on your server for entropy.          |
| **`signature`** | `string`  |  **Yes** | The 64-character SHA-256 hash computed with your Secret Key.          |
| **`params`**    | `object`  |  **Yes** | The nested payload containing all disbursement details.               |

### Parameters (`params` Object)

| Parameter                               | Type                  |           Required           | Description                                                                                                                                                  |
| :-------------------------------------- | :-------------------- | :--------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`amount`**                            | `integer`             |            **Yes**           | The amount to disburse in standard major currency units (e.g. `1000` = 1,000 KES). **Must be a whole integer with no decimal points.**                       |
| **`currency`**                          | `string`              |            **Yes**           | The 3-letter ISO currency code (e.g. `KES`, `NGN`, `ZAR`, `GHS`, `UGX`, `TZS`, `XOF`, `XAF`, `ZMW`, `RWF`, `CDF`, `SLE`).                                    |
| **`country_id`**                        | `integer`             |            **Yes**           | The country ID where the recipient is located (e.g. `1` for Kenya, `17` for Nigeria, `15` for South Africa). See [Payout Methods](/making-payments/methods). |
| **`payment_method_type`**               | `string`              |            **Yes**           | The destination payout channel (e.g. `send money`, `pay bill`, `till number`, `pesalink`, `mtn`, `airtel`, `orange`, `bank`).                                |
| **`first_name`**                        | `string`              |            **Yes**           | Recipient's first name.                                                                                                                                      |
| **`last_name`**                         | `string`              |            **Yes**           | Recipient's last name.                                                                                                                                       |
| **`mobile`**                            | `string`              |            **Yes**           | Recipient's phone number in international format without `+` (e.g. `254700000000`).                                                                          |
| **`email`**                             | `string`              |            **Yes**           | Recipient's email address.                                                                                                                                   |
| **`city`**                              | `string`              |            **Yes**           | Recipient's city.                                                                                                                                            |
| **`client_callback_url`**               | `string`              | **Required (for callbacks)** | The endpoint URL on your server where real-time terminal callbacks (`payment_step: 2`) are delivered. Essential for automated transaction settlement.        |
| **`payment_reference`**                 | `string`              |        **Recommended**       | Internal merchant reference for identifying the payout transaction and preventing duplicates.                                                                |
| **`third_party_reference_1`**           | `string`              |        **Recommended**       | Your unique merchant transaction tracking reference for ledger reconciliation and idempotency defense.                                                       |
| **`third_party_reference_2`**           | `string`              |           Optional           | Additional secondary tracking reference or batch identifier.                                                                                                 |
| **`is_third_party_reference_1_unique`** | `boolean` / `integer` |           Optional           | Set to `true` or `1` to enforce strict uniqueness on `third_party_reference_1`. Prevents duplicate payouts from being initiated.                             |
| **`is_third_party_reference_2_unique`** | `boolean` / `integer` |           Optional           | Set to `true` or `1` to enforce strict uniqueness on `third_party_reference_2`.                                                                              |
| **`sub_merchant_id`**                   | `string` (UUID)       |           Optional           | Sub-merchant ID if disbursing on behalf of an onboarded sub-merchant.                                                                                        |

### Channel-Specific Parameter Objects (`params`)

Depending on your selected `payment_method_type`, supply the matching nested array inside `params`:

| Channel / `payment_method_type`                                              | Nested Array Key                            | Required Fields in Array                                                  | Description                                                                                                       |
| :--------------------------------------------------------------------------- | :------------------------------------------ | :------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------- |
| **Mobile Money** (`send money`, `mtn`, `airtel`, `Orange`, `Vodafone`, etc.) | Matches provider (`sendmoney`, `mtn`, etc.) | `phone_number`                                                            | Recipient mobile number in international format without `+`.                                                      |
| **M-Pesa Business Paybill** (`pay bill`)                                     | `paybill`                                   | `paybill_number`, `account_number`                                        | Destination business Paybill and customer account number reference.                                               |
| **M-Pesa Buy Goods Till** (`till number`)                                    | `tillnumber`                                | `till_number`                                                             | Destination merchant Buy Goods till number.                                                                       |
| **PesaLink (Kenya)** (`pesalink`)                                            | `pesalink`                                  | `bank_name`, `bank_code`, `branch_name`, `account_name`, `account_number` | Real-time bank transfer details to commercial banks in Kenya.                                                     |
| **Direct Bank Transfer** (`bank`)                                            | `bank`                                      | `bank_name`, `bank_code`, `branch_name`, `account_name`, `account_number` | Direct bank disbursement details (e.g. Nigeria NGN NUBAN, South Africa ZAR RTC/EFT, or International bank wires). |
| **Cameroon Compliance** (XAF)                                                | Root `params` fields                        | `sender_firstname`, `sender_lastname`, `sender_phone_number`              | Mandatory CEMAC sender compliance fields for Cameroon disbursements.                                              |

<Tip>
  **Best Practice: Generate Unique References & Enable Uniqueness Checks:**\
  We strongly recommend generating `payment_reference` and `third_party_reference_1` on your server before sending the payout request (for example, using an internal transfer ID like `PAYOUT-2026-001` or `TX-DISB-9901`).

  To guarantee that duplicate transactions are never processed:

  * **Enable Uniqueness Enforcement:** Pass `"is_third_party_reference_1_unique": 1` in `params`. If a payout with the same reference has already been created, Niobi immediately rejects the duplicate with `"Duplicate third_party_reference_1 was found!"`.
  * **Instant Webhook Reconciliation:** When Niobi sends the terminal callback (`payment_step: 2`), it includes your exact reference keys so your ledger updates automatically.
  * **Direct Status Lookups:** Allows you to query the [Get Transaction Status API](/transaction-status) using your own internal ID at any time.
</Tip>

***

## 3-Step Direct Payout Integration Walkthrough

<Steps>
  <Step title="1. Initiate Disbursement Request">
    Your backend server signs the payload and dispatches a POST request to `/api/v4/niobi-unified-payments`.

    #### Sample Request: Mobile Money Payout (Kenya M-Pesa)

    ```json theme={null}
    {
      "client_id": "YOUR_CLIENT_ID",
      "sender": "YOUR_INTEGRATION_NAME",
      "timestamp": 1724835600,
      "salt": "random_salt_12345",
      "signature": "d4e5f6a7b8c90123456789abcdef0123456789abcdef0123456789abcdef01",
      "params": {
        "amount": 1000,
        "currency": "KES",
        "country_id": 1,
        "first_name": "Jane",
        "last_name": "Doe",
        "mobile": "254647647649",
        "email": "jane.doe@example.com",
        "city": "Nairobi",
        "payment_method_type": "send money",
        "sendmoney": [
          { "phone_number": "254647647649" }
        ],
        "client_callback_url": "https://yourdomain.com/niobi/payout-callback",
        "payment_reference": "PAYOUT-REF-1001",
        "third_party_reference_1": "TX-DISB-9901"
      }
    }
    ```

    #### Sample Initial API Responses (Synchronous)

    <Tabs>
      <Tab title="Initiation Successful (payment_step: 1)">
        ```json theme={null}
        {
          "success": true,
          "message": "Payment has been initiated successfully.",
          "data": {
            "amount": 1000,
            "client_callback_url": "https://yourdomain.com/niobi/payout-callback",
            "country_id": 1,
            "currency": "KES",
            "mobile": "254647647649",
            "payment_method_type": "send money",
            "payment_reference": "PAYOUT-REF-1001",
            "third_party_reference_1": "TX-DISB-9901",
            "third_party_reference_2": null,
            "status": "success",
            "payment_step": 1
          },
          "status_code": "000000"
        }
        ```
      </Tab>

      <Tab title="Initiation Failed">
        ```json theme={null}
        {
          "success": false,
          "message": "Insufficient merchant wallet balance to execute payout.",
          "data": null,
          "status_code": "000005"
        }
        ```

        If payout dispatch is rejected outright due to invalid parameters or balance constraints, refer to the [400 Client Errors Guide](/errors/client-errors).
      </Tab>
    </Tabs>

    <Note>
      **Understanding the Two Success Indicators:**

      * **Outer `"success": true` (Root level):** Indicates the HTTP request was valid, authenticated, and received by Niobi ([Request Successfully Initiated](/errors/200-success)).
      * **Inner `"status": "success"` (Inside `data`):** Indicates the payout instruction was accepted and dispatched to the upstream network switch. It does **not** signify final delivery to the recipient.
    </Note>
  </Step>

  <Step title="2. Upstream Processing by Telco / Banking Switch">
    Niobi routes the disbursement to the local payment provider or mobile network operator. The switch processes the transfer and deposits funds into the recipient's wallet or bank account.
  </Step>

  <Step title="3. Handle Final Disbursement Outcome">
    When settlement concludes, Niobi dispatches an asynchronous callback containing `"payment_step": 2` to the `client_callback_url` passed under `params.client_callback_url` in your disbursement request.

    #### Sample Terminal Callbacks (payment\_step: 2)

    <Tabs>
      <Tab title="Successful Payout (000000)">
        ```json theme={null}
        {
          "client_id": "YOUR_CLIENT_ID",
          "sender": "YOUR_INTEGRATION_NAME",
          "salt": "random_salt_12345",
          "timestamp": 1724835630,
          "signature": "a1b2c3d4e5f60718293a4b5c6d7e8f90123456789abcdef0123456789abcdef0",
          "params": {
            "amount": 1000,
            "currency": "KES",
            "mobile": "254647647649",
            "payment_method_type": "send money",
            "payment_reference": "PAYOUT-REF-1001",
            "payment_step": 2,
            "reference": "NIO-PAYOUT-789012",
            "status": "success",
            "statusCode": "000000",
            "third_party_reference_1": "TX-DISB-9901"
          }
        }
        ```
      </Tab>

      <Tab title="Failed Payout (000005)">
        ```json theme={null}
        {
          "client_id": "YOUR_CLIENT_ID",
          "sender": "YOUR_INTEGRATION_NAME",
          "salt": "random_salt_12345",
          "timestamp": 1724835635,
          "signature": "e5f6a7b8c90123456789abcdef0123456789abcdef0123456789abcdef012345",
          "params": {
            "amount": 1000,
            "currency": "KES",
            "mobile": "254647647649",
            "payment_method_type": "send money",
            "payment_reference": "PAYOUT-REF-1001",
            "payment_step": 2,
            "reference": "NIO-PAYOUT-789012",
            "status": "failed",
            "statusCode": "000005",
            "failureReason": {
              "failureCode": "DS-008",
              "failureMessage": "Recipient mobile money wallet is inactive or unregistered"
            },
            "third_party_reference_1": "TX-DISB-9901"
          }
        }
        ```
      </Tab>
    </Tabs>

    <Tip>
      **Determine Outcome Using `status` and `statusCode`:**\
      When `"payment_step": 2` arrives, inspect both `params.status` and `params.statusCode` inside the payload:

      * [`status: "success"` with `statusCode: "000000"`](/errors/000000-success): The disbursement was successfully processed and credited to the recipient. Mark the payout order as completed in your ledger.
      * [`status: "failed"` with `statusCode: "000005"`](/errors/000005-failed): The disbursement failed or was rejected by the switch. Inspect `params.failureReason` for specific provider diagnostics.

      See our [Transaction Status Codes Guide](/errors/transaction-status-codes) for the complete list of system status codes.
    </Tip>

    <Note>
      **On-Demand Status Verification:**\
      If you ever need to independently verify the status of a payout on demand, call the [Get Transaction Status API](/api-reference/get-transaction-status) (`POST /api/v3/get-unified-transaction-status`) passing `"type": "payout"` and your transaction reference in `"id"`.
    </Note>
  </Step>
</Steps>

***

## Next Steps

Follow these guides in order to complete your disbursement integration:

<div className="next-steps-flow">
  <a href="/making-payments/methods" className="next-step-card">
    <div className="next-step-badge">Step 1</div>
    <h3>Payout Methods</h3>
    <p>Explore supported channels: Mobile Money, Paybill, Till Numbers, PesaLink, and Bank Transfers.</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="/making-payments/callbacks" className="next-step-card">
    <div className="next-step-badge">Step 2</div>
    <h3>Payout Callbacks</h3>
    <p>Handle terminal payment\_step: 2 callbacks, verify signatures, and reconcile outcomes.</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="/making-payments/best-practices" className="next-step-card">
    <div className="next-step-badge">Step 3</div>
    <h3>Payout Best Practices</h3>
    <p>Wallet balance monitoring, phone number validation, idempotency, and error handling.</p>
  </a>
</div>
