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

# Payouts

> Draw your balance down to a Malawian bank account or mobile money number.

A payout moves money out of your prefunded balance to a beneficiary in
Malawi. You create a payout with one API call: `POST /payouts/bank` or
`POST /payouts/momo`. Hermes holds the funds at once. Hermes then tracks the
transfer to a terminal state.

For money that comes *into* your balance, see [Pay-ins](/pay-ins).

## The two rails

There is no `rail` field to set. **The endpoint that you call selects the
rail.**

| Rail         | Endpoint             | Destination                                   |
| ------------ | -------------------- | --------------------------------------------- |
| Mobile money | `POST /payouts/momo` | A wallet number on Airtel Money or TNM Mpamba |
| Bank         | `POST /payouts/bank` | A bank account number                         |

The two rails use the same `institution_id` from `GET /institutions`. Each
entry in that list tells you its rail. If you send an institution to the
wrong endpoint, Hermes refuses the request with `unknown_institution`. Hermes
does not send the money to a different rail. See [Quickstart](/quickstart)
for the full request body of each rail.

## Idempotency

Each create request needs an **`Idempotency-Key`** header. Make one key for
each payout that you intend to send. You can use a UUID.

Send the same key with the same body again, and Hermes returns the first
response again, with the same bytes. Because of this, a network timeout or a
second request cannot cause two payments.

The same key with a *different* body gives `409 conflict`. Use a key a second
time only to send the same payout again.

A `202` response means that Hermes **decided**: the funds are on hold and the
payout is in a queue. A `4xx` response means that **nothing happened**. There
is no payout and there are no funds on hold.

## What a payout costs

Jenzy passes payout fees to you **at cost** and adds no margin. This is the
schedule:

| Line                           | Rate                                                   |
| ------------------------------ | ------------------------------------------------------ |
| Transaction fee                | 0.94% of the amount (0.8% + VAT)                       |
| Convenience fee — mobile money | 235 MWK flat (200 + VAT)                               |
| Convenience fee — bank         | 587.50 MWK flat (500 + VAT)                            |
| Government levy                | 0.05% of the amount, only on payouts above 100,000 MWK |

The convenience fee applies to each payout, at all amounts. There is no
minimum amount, and Jenzy does not remove the fee for large amounts.

The government levy applies only above 100,000 MWK. At or below that amount,
the levy is zero.

The figures above include VAT, in a rounded form. Hermes rounds each fee line
to the tambala separately. So a fee that you calculate from these rates can be
one tambala different. `POST /fees/quote` gives the exact figure.

**Example — a payout of 10,000 MWK:**

| Line                                | Momo          | Bank          |
| ----------------------------------- | ------------- | ------------- |
| Transaction fee (0.94%)             | 94.00         | 94.00         |
| Convenience fee                     | 235.00        | 587.50        |
| Government levy (below 100,000 MWK) | 0.00          | 0.00          |
| **Total fee**                       | **329.00**    | **681.50**    |
| **Total debit** (amount + fee)      | **10,329.00** | **10,681.50** |

Two rules come from the pass-through fee:

* **The create response does not tell you the exact fee in advance.** Call
  `POST /fees/quote` with `direction: "payout"` before you send the payout.
  The quote gives the exact fee and the `total_debit`. The `total_debit` is
  the amount plus the fee, and it is the money that leaves your balance. Your
  beneficiary still gets the full `amount_mwk`. **Use `POST /fees/quote` as
  the correct source of the exact figures.** The table above is a simple
  version of your agreed commercial terms. It is not a replacement for the
  quote. The quote shows VAT as its own `vat` line and does not add VAT to
  each fee line. So compare your figures on `total_fee`, and not line by
  line.
* **Hermes knows the fee for certain only after the provider reports it.**
  The `fee` field of a payout is `null` while the transfer is in progress.
  Hermes then writes the charge from the provider into the field. Hermes
  never calculates that charge itself.

<Note>
  When Hermes accepts a payout, it holds the `amount_mwk` **and** the quoted
  fee. Your available balance must cover the two together. Your balance can
  be less than the amount, or less than the amount plus the fee. In both
  conditions, you get `insufficient_funds` (402).
</Note>

More fee rules:

* Hermes charges a fee only on a payout that **succeeds**. A `failed` payout
  always shows `fee: "0.00"`. Hermes releases the full hold, the amount and
  the fee, back to your available balance.
* A `reversed` payout keeps its fee, because the payout did succeed before
  the provider took the money back.
* A `null` fee does not mean that the payout is free. It means that Hermes
  does not know the fee yet. Do not use `null` as zero. Do not use `null` to
  decide if a payout is still in progress. The `status` field tells you that.
* Hermes calculates the hold from the fee schedule. When the payout settles,
  Hermes compares the hold with the charge from the provider. Hermes returns
  the difference to your available balance. The `fee` field always shows the
  charge that Hermes observed, and never the first quote. So the debit from
  your balance can be a small amount different from the quote.

## The payout resource

Read payouts with `GET /payouts` and `GET /payouts/{id}`. `GET /payouts` puts
the newest payout first and uses keyset pagination. The create response and
the `data` field of each payout webhook use the same shape.

```json theme={null}
{
  "id": "0d5e63b2-59f3-4d2a-8f57-2f14a3c14f6d",
  "amount_mwk": "5000.00",
  "fee": "282.00",
  "rail": "momo",
  "status": "succeeded",
  "destination": {
    "institution_id": 112400,
    "mobile_number": "265991234567",
    "beneficiary_name": "Chikondi Banda"
  },
  "failure": null,
  "created_at": "2026-07-08T09:14:02.000Z",
  "terminal_at": "2026-07-08T09:14:31.000Z"
}
```

### Status lifecycle

```
held → submitted → pending → succeeded | failed
```

* **`held`** — the funds are on hold. Hermes did not send the payout to the
  provider yet.
* **`pending`** — Hermes sent the payout and waits for the result from the
  provider. This is the only status where a payout waits. A payout can stay
  here for some time if the provider is slow to confirm. Hermes reads the
  status again. Hermes does not send the payout a second time.
* **`succeeded`** / **`failed`** — terminal. No other status is final.
* **`reversed`** — the payout succeeded, and then the provider took the money
  back. This is not a failure. The `failure` field stays `null`. The
  `terminal_at` field keeps the time of the *first* success, and not the time
  of the reversal.

Hermes sets `terminal_at` one time, when the payout first gets to `succeeded`
or `failed`. The field then never changes, not even at a later reversal.

## How to learn the result

**Webhooks (recommended)** — an org admin registers an endpoint in the Jenzy
portal. Hermes then sends `payout.succeeded`, `payout.failed`, or (not often)
`payout.reversed` a few seconds after the result. See [Webhooks](/webhooks)
for the event list and how to verify a signature.

**A poll** — read `GET /payouts/{id}` again until the status is terminal. A
poll and a webhook give the same data. So you can always use `GET /payouts`
to catch up after an outage.

When a payout fails, Hermes releases the full hold, the amount and the fee,
back to your available balance. The `failure.code` field tells you if a
second payout can succeed:

| Code                        | Can a second payout succeed?                              |
| --------------------------- | --------------------------------------------------------- |
| `recipient_account_invalid` | Only after you correct the destination.                   |
| `declined`                  | Not usually. Examine the payout before you send it again. |
| `provider_unavailable`      | Yes. Send a new payout after a short time.                |
| `temporarily_unavailable`   | Yes. Send a new payout later. Hermes took no funds.       |

Hermes never sends a failed payout again. To try again, always create a
**new** payout with a **new** `Idempotency-Key`. If you use the old key, you
get the first failure again.

<Warning>
  **A reversal needs your attention, and your balance can be late.**

  A `payout.reversed` event tells you that the provider took back a payout
  that succeeded before. The recipient no longer has the money. If you gave
  goods or services after the `payout.succeeded` event, this event tells you
  to act.

  For a short time after the event, `GET /balance` can still show the payout
  as spent. A person in Jenzy operations makes the correction manually.
  Jenzy confirms case by case if the provider returns its own fee. Use the
  event as the correct signal, and let your balance catch up.

  A reversal is not usual, and Jenzy always examines one. Speak to support if
  you get a reversal that you cannot explain.
</Warning>

## Limits

Three limits apply to each payout. The limits do not depend on your balance.

* **Per-payout maximum** — Hermes refuses one payout above the cap with
  `amount_limit_exceeded` (422).
* **Daily cap** — the payout volume of your org in the last 24 hours.
* **Velocity** — the payout count of your org in the last 1 hour.

The daily cap and the velocity limit both give `org_limit_exceeded` (429).
Send the payout again later. Hermes measures the limits on the amount that
you asked to send, and not on the amount plus the fee. See [Errors](/errors)
for the full table.

## Common rejections

Hermes refuses the request and holds **nothing**. See [Errors](/errors) for
the complete list.

* `insufficient_funds` (402) — the amount plus the fee is more than your
  available balance.
* `unknown_institution` (422) — the rail of this endpoint cannot pay the
  `institution_id`. Read `GET /institutions` again.
* `invalid_mobile_number` / `mobile_number_institution_mismatch` (422, momo
  only) — the number is not in the full international form. Or the number
  belongs to the other network.
* `invalid_account_number` (422, bank only) — the account number is not valid
  for the destination bank.
* `amount_limit_exceeded` (422) — the amount is above the per-payout maximum.
* `rate_limited` (429) / `org_limit_exceeded` (429) — send the request again
  after the given time.
