Do I generate payoutRequestId myself? What are the limits?
Yes — it is yours to generate, and it is the idempotency key. Constraints:
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:
payoutAmountpayoutCurrencypayoutInfo.payoutMethodUserAccountNo
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 samepayoutRequestId— 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 newpayoutRequestId.
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 onlypayeeId 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.
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 ofA0003:
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:
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 followpayeeType, and the two must agree:
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?
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:
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 nobankCode 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:
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:
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 withB0004 (“User info does not match”).
The comparison is narrower than people expect:
- Only first name and last name are compared.
payeeMiddleNameis not. - The comparison is case-insensitive.
- If either side’s value is blank, that part of the check passes.
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.
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.
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 for how to read it, or
contact support with the payoutRequestId.