1. Check availability
Ask what KES needs for your account. The answer lists the required fields per customer type and account type.2. Create the customer
Required for an individual:firstname, lastname, email. See Virtual account requirements for every combination.
kycStatus incomplete or pending. Anything missing for KES can be added later with PATCH /customers/{uuid} before you request the account.
3. Request the account
400 with webhook_secret_required.
The response depends on the currency. For KES, expect pending then active:
pending: The account is still being issued or approved. Wait for virtual_account.created.
active: Ready. Show the bank details to your payer.
type: "dynamic", amount, optional expiresIn):
type: "dynamic", amount and optionally expiresIn. A payment of a different amount is held for review as an underpayment or overpayment.
If the request is rejected straight away you get 422 with the reason in errors[0].reason and no account is created. If it is rejected later you receive virtual_account.failed with a reason. See Webhooks.
4. Show the details to your payer
Show these fields frombankDetails:
How your payer sends the money:
- Make a bank transfer to the account number shown, in Kenyan shillings.
5. Receive the payment
When the payment arrives you receivevirtual_account.payment.received. Amounts are decimal strings in KES; destinationAmount is in USD, after any fees.
data.transaction.reference to ignore repeats. See Webhooks.
Things to handle
- A dedicated account is permanent. A dynamic account expects one exact amount and expires (7200 seconds by default, set with
expiresIn). - When a dedicated account is still
pending, wait forvirtual_account.createdbefore showing details to your customer. - A payment can be held for review. You receive
virtual_account.payment.manual_reviewwith areview.reason, thenreceivedorrejectedafter a decision. - A dynamic account that expires sends
virtual_account.expired. A late payment is held for review. GET /virtual-accounts/{uuid}returns the current status at any time, andPOST /virtual-accounts/{uuid}/deactivatestops the account accepting payments.

