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

# Withdrawal Flows

> Disburse funds to mobile money wallets (M-PESA, MTN Money, Airtel Money, etc.)

## Mobile Money Withdrawal Flow

### Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Kotani Pay
    participant MNO as Mobile Money Network
    participant CronJob
    participant Integrator

    User->>Kotani Pay: POST /withdraw/mobile-money
    Note right of User: Can provide referenceId<br/>or system generates one
    Kotani Pay->>Kotani Pay: Validate API Key, Customer & Wallet
    Kotani Pay->>Kotani Pay: Check Balance

    alt Insufficient Balance
        Kotani Pay-->>User: 400 Insufficient Funds
    else Sufficient Balance
        Kotani Pay->>Kotani Pay: Generate/Use Transaction ID
        Kotani Pay->>Kotani Pay: Calculate Fees

        Note over Kotani Pay: IMMEDIATE DEBIT
        Kotani Pay->>Kotani Pay: Debit Wallet NOW
        Kotani Pay->>Kotani Pay: Status: INITIATED
        Kotani Pay->>Kotani Pay: Create Hash (duplicate check)

        Kotani Pay->>MNO: Initiate Disbursement
        MNO-->>Kotani Pay: Request ID
        Kotani Pay->>Kotani Pay: Status: PENDING
        Kotani Pay-->>User: Withdrawal Initiated

        alt Provider Has Webhooks
            MNO->>Kotani Pay: Status Callback
        else Polling Required
            Note over CronJob: Every 5 Minutes
            CronJob->>Kotani Pay: Check Pending
            Kotani Pay->>MNO: Query Status
            MNO-->>Kotani Pay: Current Status
        end

        alt Withdrawal Successful
            Kotani Pay->>Kotani Pay: Status: SUCCESSFUL
            MNO->>User: Funds Received

            Kotani Pay->>Integrator: Send Webhook
            alt Webhook Success
                Integrator-->>Kotani Pay: 200 OK
            else Webhook Failed
                Note over CronJob: Retry Every 2h
            end

        else Withdrawal Failed
            Kotani Pay->>Kotani Pay: Status: FAILED

            Note over Kotani Pay: AUTOMATIC REVERSAL
            Kotani Pay->>Kotani Pay: Credit Back Balance

            Kotani Pay->>Integrator: Send Failed Webhook

        else Timeout (24h)
            Kotani Pay->>Kotani Pay: Status: EXPIRED
            Kotani Pay->>Kotani Pay: Credit Back Balance
            Kotani Pay->>Integrator: Send Timeout Webhook
        end
    end

    User->>Kotani Pay: GET /withdraw/status/:reference_id
    Kotani Pay-->>User: Status & Details
```

### Details

**Available Countries**: All supported countries

**Networks**: M-PESA, MTN Money, Airtel Money, Orange Money, and more
**Speed**: Instant to few minutes

**Key Points**:

* Funds debited IMMEDIATELY when withdrawal created
* If withdrawal fails, funds automatically reversed
* Hash-based duplicate prevention

***

## Bank Transfer Withdrawal Flow

Disburse funds to bank accounts.

### Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Kotani Pay
    participant Bank as Bank Provider
    participant CronJob
    participant Integrator

    User->>Kotani Pay: POST /withdraw/bank
    Note right of User: Can provide referenceId<br/>or system generates one
    Kotani Pay->>Kotani Pay: Validate API Key, Customer & Wallet
    Kotani Pay->>Kotani Pay: Check Balance

    alt Insufficient Balance
        Kotani Pay-->>User: 400 Insufficient Funds
    else Sufficient Balance
        Kotani Pay->>Kotani Pay: Generate/Use Transaction ID
        Kotani Pay->>Kotani Pay: Calculate Fees

        Note over Kotani Pay: IMMEDIATE DEBIT
        Kotani Pay->>Kotani Pay: Debit Wallet NOW
        Kotani Pay->>Kotani Pay: Status: INITIATED

        Kotani Pay->>Bank: Initiate Bank Transfer
        Bank-->>Kotani Pay: Transfer ID
        Kotani Pay->>Kotani Pay: Status: PENDING
        Kotani Pay-->>User: Transfer Initiated

        alt Provider Has Webhooks
            Bank->>Kotani Pay: Status Callback
        else Polling Required
            Note over CronJob: Every 5 Minutes
            CronJob->>Kotani Pay: Check Pending
            Kotani Pay->>Bank: Query Status
            Bank-->>Kotani Pay: Current Status
        end

        alt Transfer Successful
            Kotani Pay->>Kotani Pay: Status: SUCCESSFUL
            Bank->>User: Funds Received
            Kotani Pay->>Integrator: Send Webhook

        else Transfer Failed
            Kotani Pay->>Kotani Pay: Status: FAILED
            Kotani Pay->>Kotani Pay: Credit Back Balance
            Kotani Pay->>Integrator: Send Failed Webhook

        else Timeout (24h)
            Kotani Pay->>Kotani Pay: Status: EXPIRED
            Kotani Pay->>Kotani Pay: Credit Back Balance
            Kotani Pay->>Integrator: Send Timeout Webhook
        end
    end

    User->>Kotani Pay: GET /withdraw/status/:reference_id
    Kotani Pay-->>User: Status & Details
```

### Details

**Available Countries**: All supported countries

**Processing**: 1-3 business days
**Requirements**: Bank account number, bank code

**Key Points**:

* Funds debited immediately
* Longer settlement time than mobile money
* Automatic reversal on failure

***

## Lipa na M-Pesa Withdrawal Flow

Send payments to merchant till numbers (Kenya only).

### Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Kotani Pay
    participant M-PESA as M-PESA Till
    participant Integrator

    User->>Kotani Pay: POST /withdraw/lipa-na-mpesa
    Note right of User: Includes tillNumber
    Kotani Pay->>Kotani Pay: Validate API Key & Wallet
    Kotani Pay->>Kotani Pay: Validate KES Currency
    Kotani Pay->>Kotani Pay: Check Balance

    alt Insufficient Balance
        Kotani Pay-->>User: 400 Insufficient Funds
    else Sufficient Balance
        Kotani Pay->>Kotani Pay: Generate/Use Transaction ID
        Kotani Pay->>Kotani Pay: Calculate Fees

        Note over Kotani Pay: IMMEDIATE DEBIT
        Kotani Pay->>Kotani Pay: Debit Wallet NOW
        Kotani Pay->>Kotani Pay: Status: INITIATED
        Kotani Pay->>Kotani Pay: Create Hash

        Kotani Pay->>M-PESA: Send to Till Number
        M-PESA-->>Kotani Pay: Request ID
        Kotani Pay->>Kotani Pay: Status: PENDING
        Kotani Pay-->>User: Payment Sent

        M-PESA->>Kotani Pay: Status Callback

        alt Payment Successful
            Kotani Pay->>Kotani Pay: Status: SUCCESSFUL
            Kotani Pay->>Integrator: Send Webhook

        else Payment Failed
            Kotani Pay->>Kotani Pay: Status: FAILED
            Kotani Pay->>Kotani Pay: Credit Back Balance
            Kotani Pay->>Integrator: Send Failed Webhook

        else Timeout (24h)
            Kotani Pay->>Kotani Pay: Status: EXPIRED
            Kotani Pay->>Kotani Pay: Credit Back Balance
            Kotani Pay->>Integrator: Send Timeout Webhook
        end
    end

    User->>Kotani Pay: GET /withdraw/status/:reference_id
    Kotani Pay-->>User: Status & Details
```

### Details

**Country**: Kenya only
**Currency**: KES
**Speed**: Instant

**Key Points**:

* No customer record needed
* Funds debited immediately
* Instant settlement

***

## Paybill Withdrawal Flow

Send payments to paybill numbers with account reference (Kenya only).

### Flow Diagram

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Kotani Pay
    participant M-PESA as M-PESA Paybill
    participant Integrator

    User->>Kotani Pay: POST /withdraw/paybill
    Note right of User: Includes paybillNumber<br/>& accountNumber
    Kotani Pay->>Kotani Pay: Validate API Key & Wallet
    Kotani Pay->>Kotani Pay: Validate KES Currency
    Kotani Pay->>Kotani Pay: Check Balance

    alt Insufficient Balance
        Kotani Pay-->>User: 400 Insufficient Funds
    else Sufficient Balance
        Kotani Pay->>Kotani Pay: Generate/Use Transaction ID
        Kotani Pay->>Kotani Pay: Calculate Fees

        Note over Kotani Pay: IMMEDIATE DEBIT
        Kotani Pay->>Kotani Pay: Debit Wallet NOW
        Kotani Pay->>Kotani Pay: Status: INITIATED
        Kotani Pay->>Kotani Pay: Create Hash

        Kotani Pay->>M-PESA: Send to Paybill
        M-PESA-->>Kotani Pay: Request ID
        Kotani Pay->>Kotani Pay: Status: PENDING
        Kotani Pay-->>User: Payment Sent

        M-PESA->>Kotani Pay: Status Callback

        alt Payment Successful
            Kotani Pay->>Kotani Pay: Status: SUCCESSFUL
            Kotani Pay->>Integrator: Send Webhook

        else Payment Failed
            Kotani Pay->>Kotani Pay: Status: FAILED
            Kotani Pay->>Kotani Pay: Credit Back Balance
            Kotani Pay->>Integrator: Send Failed Webhook

        else Timeout (24h)
            Kotani Pay->>Kotani Pay: Status: EXPIRED
            Kotani Pay->>Kotani Pay: Credit Back Balance
            Kotani Pay->>Integrator: Send Timeout Webhook
        end
    end

    User->>Kotani Pay: GET /withdraw/status/:reference_id
    Kotani Pay-->>User: Status & Details
```

### Details

**Country**: Kenya only
**Currency**: KES
**Speed**: Instant

**Key Points**:

* Requires paybill number and account reference
* No customer record needed
* Instant settlement

***

## Transaction Statuses

| Status        | Description                          | Funds Status    |
| ------------- | ------------------------------------ | --------------- |
| `INITIATED`   | Transaction created, being processed | Debited         |
| `PENDING`     | Submitted to provider                | Debited         |
| `IN_PROGRESS` | Provider processing                  | Debited         |
| `SUCCESSFUL`  | Funds disbursed successfully         | Debited (final) |
| `FAILED`      | Disbursement failed                  | **Reversed**    |
| `EXPIRED`     | No confirmation within 24h           | **Reversed**    |
| `CANCELLED`   | Transaction cancelled                | **Reversed**    |
| `DECLINED`    | Provider declined                    | **Reversed**    |
| `REVERSED`    | Transaction reversed                 | **Reversed**    |

***

## Common Features

### Immediate Wallet Debit

* Funds debited IMMEDIATELY at creation
* NOT reserved - actual debit happens instantly
* If withdrawal fails, funds automatically reversed
* No balance locking - clean debit/reverse flow

### Transaction ID

* Can be **integrator-provided** via `referenceId`
* Or **system-generated** if not provided
* Must be unique per integrator

### Automatic Reversal

When withdrawal fails or times out:

1. Status updated to FAILED/EXPIRED/REVERSED
2. Amount automatically credited back to wallet
3. Transaction recorded for audit
4. Webhook sent to notify integrator

### Duplicate Prevention

* Hash-based detection within same minute
* Composite: timestamp + integrator + phone
* Prevents accidental duplicates
* Returns error if duplicate detected

### Status Monitoring

* Webhook callbacks for instant updates
* Polling every 5 minutes for pending transactions
* 24-hour timeout for unconfirmed transactions

***

## Error Handling

### Insufficient Balance

```json theme={null}
{
  "statusCode": 400,
  "message": "Insufficient funds in fiat wallet"
}
```

### Invalid Recipient

```json theme={null}
{
  "status": "FAILED",
  "error": "Invalid mobile money number or bank account"
}
```

### Provider Timeout

```json theme={null}
{
  "status": "EXPIRED",
  "error": "Withdrawal not confirmed within 24 hours",
  "note": "Funds reversed to wallet"
}
```

### Duplicate Transaction

```json theme={null}
{
  "statusCode": 400,
  "message": "Transaction Denied",
  "note": "Duplicate detected within same minute"
}
```

### Duplicate Reference

```json theme={null}
{
  "statusCode": 400,
  "message": "Duplicate referenceId"
}
```

### Wrong Currency (Lipa/Paybill)

```json theme={null}
{
  "statusCode": 400,
  "message": "Lipa na M-Pesa/Paybill only available for Kenya (KES)"
}
```

***

## Best Practices

* **Validate Recipients**: Use `/customers/validate-mobile-money` before withdrawals
* **Unique References**: Always provide unique `referenceId` values
* **Implement Webhooks**: Set up webhook endpoint for real-time updates
* **Handle Reversals**: Monitor for FAILED/EXPIRED statuses - funds auto-reversed
* **Idempotency**: Safe to retry with same `referenceId` if request fails

***

## Testing

Use sandbox mode with test credentials provided in sandbox documentation.
