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

# Payouts

A payout sends money **out** to a recipient — a wallet or a bank account. This is a different thing from a [refund](/essentials/payments-guide/refunds), which sends money **back** to a payer who already paid you. Payouts use a different endpoint, a different lifecycle, and a different set of status values than payments or refunds. Do not mix the two up.

## Payout methods

The request body is the same shape for every method — only `payout_type` and `payout_method_data` change.

| Method         | `payout_type` | `payout_method_data`    | Required fields             |
| -------------- | ------------- | ----------------------- | --------------------------- |
| M-Pesa Express | `wallet`      | `wallet.m_pesa_express` | —                           |
| Airtel Money   | `wallet`      | `wallet.airtel_money`   | —                           |
| MTN MoMo       | `wallet`      | `wallet.momo_mtn`       | —                           |
| PesaLink       | `bank`        | `bank.pesa_link`        | `account_id`, `bank_code`   |
| Paybill        | `bank`        | `bank.pay_bill`         | `account_id`, `sub_account` |
| Paytill        | `bank`        | `bank.pay_till`         | `account_id`                |

<Note>
  MTN MoMo payouts use `currency: "EUR"`. The other five methods use `currency: "KES"`.
</Note>

The three wallet methods take an empty object, since the recipient is identified by `phone` and `phone_country_code`. The three bank methods need their own `payout_method_data`:

**PesaLink** — `payout_type: "bank"`

```json theme={"system"}
{
  "payout_method_data": {
    "bank": {
      "pesa_link": {
        "account_id": "049384308738",
        "bank_code": "01"
      }
    }
  }
}
```

For valid `bank_code` values, see the [PesaPay bank codes](/api-reference/payouts/pesapay--bank-codes) page.

**Paybill** — `payout_type: "bank"`

```json theme={"system"}
{
  "payout_method_data": {
    "bank": {
      "pay_bill": {
        "account_id": "247247",
        "sub_account": "384539875"
      }
    }
  }
}
```

**Paytill** — `payout_type: "bank"`

```json theme={"system"}
{
  "payout_method_data": {
    "bank": {
      "pay_till": {
        "account_id": "247247"
      }
    }
  }
}
```

The account numbers above are illustrative — replace them with the recipient's actual paybill, till, or PesaLink account details.

<Steps>
  <Step title="Create and confirm the payout">
    Send a `POST` request to `/payouts/create` with `confirm: true`. Along with `amount`, `currency`, `profile_id`, `phone`, and `phone_country_code`, you choose a `payout_type` and fill in the matching `payout_method_data`.

    Here is a full example for M-Pesa Express:

    <CodeGroup>
      ```bash curl theme={"system"}
      curl --request POST \
        --url https://api.sandbox.pesaswap.io/payouts/create \
        --header "api-key: {{api_key}}" \
        --header "Content-Type: application/json" \
        --data '{
          "amount": 100,
          "profile_id": "{{profile_id}}",
          "currency": "KES",
          "confirm": true,
          "payout_type": "wallet",
          "phone": "{{phone}}",
          "phone_country_code": "254",
          "payout_method_data": {
            "wallet": {
              "m_pesa_express": {}
            }
          }
        }'
      ```

      ```javascript Node.js theme={"system"}
      const response = await fetch("https://api.sandbox.pesaswap.io/payouts/create", {
        method: "POST",
        headers: {
          "api-key": "{{api_key}}",
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          amount: 100,
          profile_id: "{{profile_id}}",
          currency: "KES",
          confirm: true,
          payout_type: "wallet",
          phone: "{{phone}}",
          phone_country_code: "254",
          payout_method_data: {
            wallet: {
              m_pesa_express: {},
            },
          },
        }),
      });

      const payout = await response.json();
      console.log(payout);
      ```

      ```python Python theme={"system"}
      import requests

      response = requests.post(
          "https://api.sandbox.pesaswap.io/payouts/create",
          headers={
              "api-key": "{{api_key}}",
              "Content-Type": "application/json",
          },
          json={
              "amount": 100,
              "profile_id": "{{profile_id}}",
              "currency": "KES",
              "confirm": True,
              "payout_type": "wallet",
              "phone": "{{phone}}",
              "phone_country_code": "254",
              "payout_method_data": {
                  "wallet": {
                      "m_pesa_express": {}
                  }
              },
          },
      )

      payout = response.json()
      print(payout)
      ```

      ```php PHP theme={"system"}
      <?php
      $curl = curl_init();

      curl_setopt_array($curl, [
          CURLOPT_URL => "https://api.sandbox.pesaswap.io/payouts/create",
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_CUSTOMREQUEST => "POST",
          CURLOPT_HTTPHEADER => [
              "api-key: {{api_key}}",
              "Content-Type: application/json",
          ],
          CURLOPT_POSTFIELDS => json_encode([
              "amount" => 100,
              "profile_id" => "{{profile_id}}",
              "currency" => "KES",
              "confirm" => true,
              "payout_type" => "wallet",
              "phone" => "{{phone}}",
              "phone_country_code" => "254",
              "payout_method_data" => [
                  "wallet" => [
                      "m_pesa_express" => new stdClass(),
                  ],
              ],
          ]),
      ]);

      $response = curl_exec($curl);
      curl_close($curl);

      echo $response;
      ```
    </CodeGroup>

    A successful call returns a `200` with a payout object. The fields that matter right now:

    ```json theme={"system"}
    {
      "payout_id": "payout_...",
      "status": "pending",
      "amount": 100,
      "currency": "KES",
      "connector": "..."
    }
    ```

    The payout has been accepted for processing, but it has not necessarily reached the recipient yet.
  </Step>

  <Step title="Poll for the final status">
    Poll the payout with `force_sync=true` until the status settles:

    <CodeGroup>
      ```bash curl theme={"system"}
      curl --request GET \
        --url "https://api.sandbox.pesaswap.io/payouts/{payout_id}?force_sync=true" \
        --header "api-key: {{api_key}}"
      ```

      ```javascript Node.js theme={"system"}
      const response = await fetch(
        `https://api.sandbox.pesaswap.io/payouts/${payoutId}?force_sync=true`,
        {
          method: "GET",
          headers: {
            "api-key": "{{api_key}}",
          },
        }
      );

      const payout = await response.json();
      console.log(payout);
      ```

      ```python Python theme={"system"}
      import requests

      response = requests.get(
          f"https://api.sandbox.pesaswap.io/payouts/{payout_id}",
          params={"force_sync": "true"},
          headers={"api-key": "{{api_key}}"},
      )

      payout = response.json()
      print(payout)
      ```

      ```php PHP theme={"system"}
      <?php
      $curl = curl_init();

      curl_setopt_array($curl, [
          CURLOPT_URL => "https://api.sandbox.pesaswap.io/payouts/{$payoutId}?force_sync=true",
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_CUSTOMREQUEST => "GET",
          CURLOPT_HTTPHEADER => [
              "api-key: {{api_key}}",
          ],
      ]);

      $response = curl_exec($curl);
      curl_close($curl);

      echo $response;
      ```
    </CodeGroup>
  </Step>
</Steps>

## Status reference

These are the only values `status` can take on a payout (`PayoutStatus`):

| Status                             | What it means                                                     | What to do         |
| ---------------------------------- | ----------------------------------------------------------------- | ------------------ |
| `requires_creation`                | The payout has not been created yet.                              | Keep polling.      |
| `requires_confirmation`            | Created but not yet confirmed.                                    | Keep polling.      |
| `requires_payout_method_data`      | Missing the data needed for the chosen payout method.             | Keep polling.      |
| `requires_fulfillment`             | Confirmed and waiting to be fulfilled.                            | Keep polling.      |
| `requires_vendor_account_creation` | Waiting on a vendor-side account to be created for the recipient. | Keep polling.      |
| `pending`                          | Accepted and queued for processing.                               | Keep polling.      |
| `initiated`                        | Sent to the connector.                                            | Keep polling.      |
| `success`                          | The payout completed. Money has reached the recipient.            | Stop — settled.    |
| `failed`                           | The payout did not complete.                                      | Stop — failed.     |
| `cancelled`                        | The payout was cancelled before completion.                       | Stop — cancelled.  |
| `expired`                          | The payout was not completed in time and expired.                 | Stop — expired.    |
| `reversed`                         | A completed payout was reversed.                                  | Stop — reversed.   |
| `ineligible`                       | The payout is not eligible to be processed.                       | Stop — ineligible. |

<Warning>
  The terminal success value is **`success`**, not `succeeded`. `succeeded` is a payment status — it does not exist on payouts. Checking for the wrong string is a common source of payouts that look "stuck" when they actually completed.
</Warning>

## Common errors

<AccordionGroup>
  <Accordion title="Missing account_id on a bank payout">
    PesaLink, Paybill, and Paytill all require `account_id` in `payout_method_data.bank`. Omitting it fails validation before the payout is even created.
  </Accordion>

  <Accordion title="Wallet payout to an unregistered number">
    If `phone` is not registered for the wallet named in `payout_type` (for example, a number with no M-Pesa account), the payout fails at the connector even though the request itself is valid.
  </Accordion>

  <Accordion title="Checking for succeeded instead of success">
    Payout completion is `success`. Code copied from a payments integration that checks for `succeeded` will never match and will poll forever.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Refunds" icon="rotate-ccw" href="/essentials/payments-guide/refunds">
    Send money back to a payer instead.
  </Card>

  <Card title="Quickstart" icon="rocket" href="/essentials/payments-guide/quickstart">
    The full create-and-poll payment loop.
  </Card>

  <Card title="Wallet payments" icon="wallet" href="/essentials/payments-guide/wallet-payments">
    M-Pesa, Airtel, and MTN MoMo payments.
  </Card>
</CardGroup>
