Skip to content

Create a beneficiary

POST
/api/global/beneficiaries
curl -X POST "https://api.payhero.africa/api/global/beneficiaries" \
-u "API_USERNAME:API_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"external_reference": "customer-8821",
"name": "John Doe",
"country": "KE",
"phone": "+254712345678",
"address": "Moi Drive, Umoja",
"date_of_birth": "10/03/1997",
"email": "john.doe@example.com",
"id_number": "32460968",
"id_type": "national_id",
"account_name": "John Doe",
"account_number": "+254712345678",
"account_type": "momo"
}'

Stores a counterparty’s identity and KYC once, and returns a uuid you then send as customer.customer_uuid on every payment for that counterparty.

A beneficiary holds identity and KYC only — provider routing (provider_config) is never stored here and always stays on the payment request.

name, country and email are the floor: without all three the record is rejected. Supplying phone, address, date_of_birth, id_number and id_type as well promotes it to kyc_level: full. kyc_level is recomputed on every write and is never set directly.

Passing external_reference makes this call idempotent — repeating it with the same reference returns the existing record instead of creating a duplicate.

Media typeapplication/json
object
name

Full name, split on the first space. Required on create unless you send first_name + last_name instead, which win if both are present.

string
first_name
string
last_name
string
surname
string
email
required

Required for tier_0, and therefore for every beneficiary.

string format: email
country
required

Required for tier_0. ISO 3166-1 alpha-2 code (or country name).

string
phone

Required for full. Returned as phone_number.

string
address

Required for full.

string
date_of_birth

Required for full. Accepts mm/dd/yyyy (what the cross-border providers document), yyyy-mm-dd or RFC 3339. Returned as RFC 3339.

string
id_number

Required for full. Stored and returned as national_id. Unique per account — the same person can be a beneficiary of several merchants.

string
id_type

Required for full. Must be NIN in Nigeria.

string
additional_id_type

Required for full in Nigeria, where it must be BVN.

string
additional_id_number

Required for full in Nigeria.

string
gender
string
marital_status
string
account_name

Destination account holder name.

string
account_number

Destination account. Backfills payment_config.account_number on payments that reference this beneficiary.

string
account_type

Destination account type, e.g. momo or bank.

string
external_reference

Your own identifier for this counterparty. Unique per account, and makes POST idempotent.

string
customer_type
string
default: retail
Allowed values: retail institution
Examples

Reduced KYC — the smallest payload the API accepts

{
"name": "John Doe",
"country": "KE",
"email": "john.doe@example.com"
}

The created (or, with a repeated external_reference, the existing) beneficiary.

Media typeapplication/json

A stored counterparty. Note that a few fields are named differently on the way out than on the way in: phone is returned as phone_number, and id_number as national_id.

object
id
integer
uuid

Send this as customer.customer_uuid on payments.

string format: uuid
first_name
string
last_name
string
surname
string | null
national_id

The id_number you sent.

string | null
date_of_birth
string | null format: date-time
gender
string | null
phone_number

The phone you sent.

string | null
email
string format: email
address
string | null
country
string
account_id

The PayHero account that owns this beneficiary, derived from your API key.

integer
account_name
string | null
account_number
string | null
account_type
string | null
customer_type
string
kyc_level

Recomputed on every write from the metadata actually present. Never settable.

string
Allowed values: tier_0 full
tier_0_lifetime_usd
number
is_active
boolean
created_at
string format: date-time
updated_at
string format: date-time
kyc

The beneficiary’s KYC standing, recomputed on every write. Read-only.

object
level
string
Allowed values: tier_0 full
full_kyc
boolean
missing_full_kyc_fields

Fields still needed to reach full. Send these to the update endpoint.

Array<string>
missing_reduced_kyc_fields

Fields still needed to reach tier_0. Non-empty only on a rejected create.

Array<string>
tier_0_lifetime_usd

Lifetime USD total this beneficiary has transacted while at tier_0.

number
tier_0_remaining_usd

Headroom left before full KYC becomes mandatory.

number
tier_0_limit_reached
boolean
tier_0_max_transaction_usd

Per-transaction ceiling while at tier_0.

number
tier_0_excluded_currencies

Currencies a tier_0 beneficiary can never transact in.

Array<string>
requires_additional_id

True when the country needs a second ID (Nigeria) and it has not been supplied.

boolean
Examples
{
"id": 94,
"uuid": "a2e0434e-17ae-41b1-a1cf-1694ad63ee99",
"first_name": "John",
"last_name": "Doe",
"surname": null,
"national_id": null,
"date_of_birth": null,
"gender": null,
"phone_number": null,
"email": "john.doe@example.com",
"address": null,
"country": "KE",
"account_id": 63,
"account_name": null,
"account_number": null,
"account_type": null,
"customer_type": "retail",
"kyc_level": "tier_0",
"tier_0_lifetime_usd": 0,
"is_active": true,
"created_at": "2026-09-05T09:41:12.113306Z",
"updated_at": "2026-09-05T09:41:12.113306Z",
"kyc": {
"level": "tier_0",
"full_kyc": false,
"missing_full_kyc_fields": [
"address",
"date_of_birth",
"id_number",
"id_type",
"phone"
],
"missing_reduced_kyc_fields": [],
"tier_0_lifetime_usd": 0,
"tier_0_remaining_usd": 200,
"tier_0_limit_reached": false,
"tier_0_max_transaction_usd": 20,
"tier_0_excluded_currencies": [
"BWP",
"NGN",
"ZAR"
],
"requires_additional_id": false
}
}

The request was rejected (validation or business-rule failure).

Media typeapplication/json
object
error_code
string
error_message
string
status_code
integer
Examples
{
"error_code": "invalid_argument",
"error_message": "the transaction amount is insufficient as it wont cater for cost: (4.62)",
"status_code": 400
}

Missing or invalid Basic auth credentials.

Media typeapplication/json
object
error_code
string
error_message
string
status_code
integer
Example
{
"error_code": "invalid_argument",
"error_message": "the transaction amount is insufficient as it wont cater for cost: (4.62)",
"status_code": 400
}