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

# Payout Request Field Guide

> Field-by-field answers for building a payout create request: how payoutRequestId idempotency actually compares requests, which payee fields are conditional by country, the two different date formats, phone and PIX key formats, bank properties per corridor, purpose values, amount precision, and which fields the API rejects.

This page is the field-level companion to the [Payout FAQ](/docs/en/support/payout-faq): what to put
in each field of a payout create request, and why a value that looks correct is rejected. For
which corridors you are allowed to pay out to at all, see
[Payout Wallet & coverage](/docs/en/support/payout-wallet-and-coverage-faq). For decoding a rejection,
see [Payout error codes & retries](/docs/en/support/payout-error-codes-and-retry-faq).

## Do I generate `payoutRequestId` myself? What are the limits?

Yes — it is yours to generate, and it is the idempotency key. Constraints:

| Property | Value |
| - | - |
| Who generates it | You |
| Maximum length | 64 characters |
| Required | Yes, on every create |
| Uniqueness scope | Per merchant account and per request channel (API payouts and Portal batch payouts are separate spaces) |

Waffo returns its own `payoutOrderId` alongside it. Either one can be used to inquire:
`/api/v1/payout/inquiry` accepts `payoutRequestId` **or** `payoutOrderId`, and if you send both,
`payoutOrderId` wins.

## What exactly happens if I reuse the same `payoutRequestId`?

Waffo looks up the existing order and compares **three** fields of your new request against it:

* `payoutAmount`
* `payoutCurrency`
* `payoutInfo.payoutMethodUserAccountNo`

Then:

| Case | Result |
| - | - |
| All three match | The **original** order is returned, with its current status. No second payout is created. |
| Any of the three differs | Rejected with `A0011` (idempotency parameter mismatch). Nothing is created. |
| The original order already ended in failure | The original order is returned **with its original failure code**, not a fresh attempt. |
| Two identical creates arrive concurrently | One is rejected with `A0013`; the other proceeds. |

Two consequences that catch people out:

* **Changing the payee's name, ID, address, country or method while keeping the amount, currency
  and account number identical does not trigger `A0011`.** The comparison does not cover those
  fields, so you get the original order back and your edited details are silently ignored. Never
  edit a payee and resend under the same `payoutRequestId` — issue a new one.
* **You cannot re-send a failed payout under the same `payoutRequestId`.** It returns the old
  failure. To retry a failed payout, create a new order with a new `payoutRequestId`.

## The create request returned `A0013` "Too many requests". Am I being rate limited?

On payout create, no. `A0013` there means a second create for the **same `payoutRequestId`** was
already in flight — the request is serialised per request id, and the loser is rejected. It is a
duplicate-submission guard, not a throughput limit. Retry the same call after a short pause, or
better, inquire first: the first attempt may well have succeeded.

## Which payee fields are actually required?

The request schema requires only `payeeId` and `payeeType` inside `payeeInfo`. Everything else is
conditional — it becomes required because of the receiving country, the payout method, or the
receiving side's own rules.

| Field | Constraint |
| - | - |
| `payeeId` | Required. Your own identifier for the payee. |
| `payeeType` | Required. `INDIVIDUAL` or `BUSINESS`; any other value is rejected. |
| `payeeFirstName` / `payeeMiddleName` / `payeeLastName` | Max 60 characters each. Conditional by corridor. |
| `payeeFullName` | Available where a corridor takes a single unsplit name. |
| `nationality` | 3-letter country code, max 3 characters. |
| `payeePhone` | Format `+{country code}-{number}`, e.g. `+62-08990013157`. Validated when present. |
| `payeeEmail` | Validated when present. |
| `payeeBirthDay`, `payeeIDIssueDate`, `payeeIDExpiryDate` | `dd/mm/yyyy`. Validated when present. |
| `payeeIDType`, `payeeIDNumber` | Conditional by country — see below. |

`addressInfo` (`address`, `city`, `region`, `postcode`, `addressCountryCode`) is a separate,
optional object. Nothing in the generic validation makes it mandatory; a corridor that needs it
rejects the payout at the pre-check step with a missing-required-field error naming it, so send it
when you have it.

## Why are there two different date formats?

Because the payee's documents and the request envelope are validated by different rules, and this
is a common source of `A0003`:

| Field | Format | Example |
| - | - | - |
| `payeeBirthDay` | `dd/mm/yyyy` | `05/09/1990` |
| `payeeIDIssueDate` | `dd/mm/yyyy` | `12/01/2021` |
| `payeeIDExpiryDate` | `dd/mm/yyyy` | `12/01/2031` |
| `payoutRequestedAt` | ISO 8601, `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` | `2026-09-07T02:15:30.000Z` |
| `payoutArrivalTime`, `payoutUpdatedAt` (response) | ISO 8601, same shape | |

## Does the phone number need a country code? What about the leading zero?

Send it as `+{country code}-{number}`. The leading `0` of a national number is dropped by some
corridors and not by others, so follow the corridor's own guidance rather than a general rule. For
eWallet payouts the phone number may double as the account identifier, and there the format is set
by the receiving wallet, not by this field — see the account-number question below.

## Which `payeeIDType` values are accepted, and why was `PASSPORT` rejected?

The defined values are `ID`, `PASSPORT`, `CPF` and `CNPJ`, but the accepted **set is narrowed per
country**, and a value outside that country's set is rejected even though it is a valid enum
member:

| Receiving country | `payeeIDType` accepted | Required? |
| - | - | - |
| Mainland China (`CHN`) | `ID`, `PASSPORT` | Yes — the request is rejected without it |
| Brazil (`BRA`) | `CPF`, `CNPJ` | Yes, together with `payeeIDNumber` |
| Other countries | No country-level restriction; the receiving side validates | Per corridor |

So `PASSPORT` on a Brazil payout is rejected because Brazil's accepted set is `CPF` / `CNPJ` only.
The rejection message names the field and lists the allowed values.

## Brazil: when do I use CPF and when CNPJ?

They follow `payeeType`, and the two must agree:

| `payeeType` | Document type | `payeeIDNumber` format |
| - | - | - |
| `INDIVIDUAL` | `CPF` | 11 digits |
| `BUSINESS` | `CNPJ` | 14 digits |

If you omit `payeeIDType`, it is inferred from `payeeType` on that basis. If you send it and it
contradicts `payeeType` — `CNPJ` with `INDIVIDUAL`, say — the payout is rejected. `payeeIDNumber`
is always required for this corridor and is checked against the digit-count pattern above.

## What is the difference between `INDIVIDUAL` and `BUSINESS`?

`payeeType` selects which payout arrangements can serve the request: a contracted method may
accept individuals, companies, or both, and a payout whose `payeeType` no corridor accepts is
rejected as not contracted. Beyond routing, the practical difference is the document rules above —
for Brazil, `BUSINESS` means a `CNPJ`. There is no separate "corporate payout" object to fill in.

## Do I need to register a payee before paying them?

No. There is no payee vault and no payee-registration call: every payout create carries the payee
details in full, and Waffo does not reuse details from a previous order. Keep your own payee
records and populate the request from them each time.

## Which `payoutMethodType` do I choose, and when is `payoutMethodName` required?

| `payoutMethodType` | `payoutMethodName` | `payoutMethodProperty` |
| - | - | - |
| `EWALLET` | **Required** — the wallet's name; a blank value is rejected | Not required |
| `BANK_ACCOUNT` | Leave empty | **Required** — a JSON object; a blank value is rejected |

Those are the only two values. `productName` is validated against the products your account has,
so a payout product you are not set up for is rejected on that field; `ONE_TIME_PAYOUT` is the
value for a single, non-recurring payout.

## What goes in `payoutMethodProperty` for a bank payout?

It is a JSON string. The keys that matter, and where they are enforced:

| Key | When it is required |
| - | - |
| `bankCode` | Required for every `BANK_ACCOUNT` payout **except** mainland China |
| `bankName` | Required for mainland China, and must contain Chinese characters |
| `bankSub` | Required for mainland China when the card number does **not** begin with `62`, and must contain Chinese characters. Used as the branch identifier in other corridors. |
| `accountType` | Required by some corridors — for Brazil one of `CRN`, `PAC`, `SVN` |
| `accountName` | Optional; supply it where the account is held under a name you cannot express through the `payeeInfo` name fields |

Malformed JSON in this field is itself an `A0003`. Where a corridor keeps per-bank branch and
account-number patterns, the pre-check step validates against them and rejects a mismatch with a
missing-or-invalid-field error naming `bankSub` or the account number.

## Where do I get bank codes from?

There is no public bank-code lookup endpoint, and no downloadable bank list in the documentation.
Bank codes are corridor-specific: ask Waffo for the list covering the corridor in your payout
agreement before you build against it. Mainland China is the exception that needs no `bankCode` at
all — send `bankName` (in Chinese) and, for cards not starting with `62`, `bankSub`.

## For a China bank payout, does the account name need to be Chinese?

`bankName` must contain Chinese characters, and so must `bankSub` where `bankSub` applies. The
account holder's name itself goes in the `payeeInfo` name fields; use `accountName` inside
`payoutMethodProperty` only where the account is held under a name those fields cannot carry.

## What do I put in `payoutMethodUserAccountNo`?

It depends on the method:

| Method | Value |
| - | - |
| `BANK_ACCOUNT` | The payee's bank account number |
| `EWALLET` | Either the wallet's own public user identifier for the payee, or — where the wallet accepts it — the payee's mobile number in the format that wallet specifies |

For a wallet that accepts a phone number, the digits are the wallet's own format, not the
`payeePhone` format: national leading zeros are dropped and the country code is included, so an
Indonesian number is sent starting `628`, not `0` or `+62-0`.

## Brazil PIX: which key types work? Is a random (EVP) key allowed?

All five standard key types are accepted, and the type is inferred from the value itself — there
is no key-type field to set:

| Key type | Accepted form |
| - | - |
| CPF | 11 digits |
| CNPJ | 14 digits |
| Phone | E.164, e.g. `+5511987654321` |
| Email | A valid email address |
| EVP (random key) | A UUID, as issued by the payee's bank |

So yes, EVP works. A value matching none of the five is rejected on
`payoutMethodUserAccountNo`.

## What happens if the account number and the payee name do not match?

Where the receiving side returns the account holder's name during the pre-check, Waffo compares it
with what you sent and fails the payout on a mismatch with `B0004` ("User info does not match").
The comparison is narrower than people expect:

* Only **first name** and **last name** are compared. `payeeMiddleName` is not.
* The comparison is **case-insensitive**.
* If either side's value is blank, that part of the check passes.

So the check is real, but it cannot be relied on as full name verification: a corridor that returns
no name performs no check at all, and a payout can succeed to an account whose holder differs from
the name you supplied. Verify payee details on your side before sending.

## Is `purpose` required? What are the valid values?

It is required for **mainland China** payouts and validated against a fixed list. For other
countries it is not required by the country validation, though an individual corridor may reject a
payout without it.

| `purpose` |
| - |
| `SALARY_PAYMENT` |
| `SERVICE_CHARGES` |
| `ADVISORY_FEES` |
| `ADVERTISING_EXPENSES` |
| `CONSTRUCTION_EXPENSES` |
| `PROPERTY_RENTAL` |
| `TAX_PAYMENT` |
| `LIBERALIZED_REMITTANCE` |

A value outside the list is rejected with the offending value echoed back. Choose the one that
describes the payment truthfully; it is a regulatory declaration, not a label.

## Is `description` shown to the payee? Will it be truncated?

Yes — it appears on the payee's bank or wallet statement, which is why it is a required field. Max
128 characters, but write it much shorter than that: receiving banks and wallets truncate, so aim
for roughly 16 English words or 10 Chinese characters and put the essential identifier first.
Waffo does not truncate it for you; the receiving side does, unpredictably.

## What can I put in `extendInfo`, and do I get it back?

A JSON object serialised as a string, e.g. `{"key1":"value1","key2":"value2"}`. It is returned
unchanged on `/api/v1/payout/inquiry` and in the payout webhook. A value that does not parse as
JSON is rejected with `A0003`. It is the right place for your own correlation ids; it is not
carried to the payee.

## How many decimal places can the amount have?

`payoutAmount` is a string, and the accepted precision is **the receiving currency's own**, not a
fixed number of places. A value with more decimals than the currency permits is **rejected** with
`A0003` naming `payoutAmount` — it is never silently rounded. The field's own pattern allows up to
8 decimal places, so the currency check, not the pattern, is what your code has to satisfy.

Amounts are also bounded by the minimum and maximum in your payout agreement for that corridor,
which is where an in-range-looking amount gets an `A0008` instead. Both bounds are on the
Service → Payout page in the Portal.

## Can I set the currency the payee receives?

Not on the request. On the single-payout API, `payeeCurrency` and `payeeAmount` are **rejected if
you send them** — they are outputs, not inputs. You set `payoutCurrency` and `payoutAmount`; the
payee currency comes from the currency pair in your payout agreement for that corridor.

For a cross-currency payout the exact received amount may not be known when the order is created,
so `payeeAmount` can come back empty on the first inquiry and be populated later. Do not promise
your payee an exact figure at request time. What is deducted from you is `prefundAmount` — the
converted amount plus that order's fee — described in
[Payout Wallet & coverage](/docs/en/support/payout-wallet-and-coverage-faq).

## Is `notifyUrl` optional? Does it override anything configured in the Portal?

For payouts created through the API, `notifyUrl` is **required** — a create without it is rejected.
It must start with `http://` or `https://`. There is no account-level payout notification URL to
fall back on and nothing to override: the payout webhook goes to the URL on that order and nowhere
else, so a payout created without one is a payout you will never be notified about.

## What is `merchantInfo` for?

`merchantInfo.merchantId` is required and is your Waffo merchant id; `merchantInfo.subMerchantId`
is optional and only relevant if your account is set up with sub-merchants. Both are echoed back on
inquiry.

***

Rejected on a field this page does not explain? The `A0003` message names the field — see
[Payout error codes & retries](/docs/en/support/payout-error-codes-and-retry-faq) for how to read it, or
[contact support](mailto:support@waffo.com) with the `payoutRequestId`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.