Integration guide
This is the end-to-end path for integrating global payments. Follow it in order — each step produces something the next one needs.
The path at a glance
Section titled “The path at a glance”- Get API credentials — a username and password from your dashboard.
- Have providers enabled on your account. Without this, discovery returns nothing.
- Discover what the payer’s country supports.
- Choose a rail, a provider and a network from that answer.
- Identify the customer — inline, or by a saved beneficiary.
- Create the payment.
- Complete it the way the rail requires — a phone prompt, a bank deposit, or a card page.
- Confirm the result from the callback.
1. Get API credentials
Section titled “1. Get API credentials”Generate an API key on your PayHero dashboard. It gives you a username and password, sent as HTTP Basic auth on every request. See Authentication.
export PH_API_USERNAME="your-username"export PH_API_PASSWORD="your-password"export PH_BASE_URL="https://api.payhero.africa"Your account id is also needed — it is the vendor_config.vendor_id on every
payment.
2. Have providers enabled on your account
Section titled “2. Have providers enabled on your account”Check it worked with step 3: if available_providers comes back empty for a
country you expect to support, this step is the reason.
3. Discover what the country supports
Section titled “3. Discover what the country supports”curl "$PH_BASE_URL/api/global/discovery/discover-rails?country=UG" \ -u "$PH_API_USERNAME:$PH_API_PASSWORD"The answer tells you, for that one country:
| Field | What it tells you |
|---|---|
rails |
Which rails work: momo, bank, card, crypto — true or false |
available_providers |
The provider codes on each rail |
provider_networks |
Every network each provider offers — banks, wallets, card schemes |
required_fields |
The fields each provider insists on, so you know what to send before sending |
currency |
The country’s currency |
Discovery answers are cacheable for a few minutes — you do not need to call it before every payment. See How routing works for the full response.
4. Choose a rail, a provider and a network
Section titled “4. Choose a rail, a provider and a network”- Pick a rail whose value in
railsistrue. It becomestransaction_channel. - Pick a provider code from
available_providers[rail]. It becomesprovider. - From
provider_networks[provider], pick a network whoseramp_typematches the direction —depositfor a collection,withdrawfor a payout. - Copy that network’s fields into
provider_config, as mapped in How routing works.
5. Identify the customer
Section titled “5. Identify the customer”customer is the owner of the funds being moved — for a collection, the payer.
Send their details inline:
"customer": { "first_name": "Jane", "last_name": "Mukasa", "email": "jane@example.com", "phone": "+256769759910", "country": "UG"}Or save them once as a beneficiary and send only
"customer": { "customer_uuid": "…" } on every later payment.
Collections are first-party: the account paying must be in the payer’s own name. Your merchant KYC tier decides whether you may collect from third parties and across borders.
6. Create the payment
Section titled “6. Create the payment”curl -X POST "$PH_BASE_URL/api/global/payments" \ -u "$PH_API_USERNAME:$PH_API_PASSWORD" \ -H "Content-Type: application/json" \ -d '{ "request_type": "payment", "transaction_channel": "momo", "provider": "yc", "amount": 20000, "currency": "UGX", "country": "UG", "customer": { "first_name": "Jane", "last_name": "Mukasa", "email": "jane@example.com", "phone": "+256769759910", "country": "UG" }, "vendor_config": { "vendor_id": 63 }, "provider_config": { "network_id": "04a3083a-567c-4a2b-aa39-fa8f19e64341", "network_name": "Airtel Mobile Money", "network_code": "AIRTEL_UGANDA", "channel_id": "e167def0-c4f0-46e2-aaa9-50046f13b0a7", "account_type": "momo" }, "payment_config": { "reference": "order_INV-2026-001", "account_number": "+256769759910", "callback_url": "https://your-system.com/webhooks/payhero", "redirect_url": "https://your-system.com/payments/return" } }'Use request_type: withdrawal for a payout — see Global Payouts.
7. Complete it the way the rail requires
Section titled “7. Complete it the way the rail requires”The create call starts the payment. What happens next depends on the rail:
| Rail | What the payer does | What you do |
|---|---|---|
momo |
Approves a prompt on their phone | Show “check your phone” and wait for the callback |
bank |
Sends a bank deposit | Show manual_payment and bank_info from the response |
card |
Enters card details on a hosted page | Send the payer to checkout_url; they come back to your redirect_url |
8. Confirm the result
Section titled “8. Confirm the result”The callback is the source of truth. The final result is POSTed to your
callback_url — see Callbacks & Webhooks.
If a callback has not arrived, look the payment up with
POST /api/global/transaction-status using the merchant_reference from the
create response.
Going live checklist
Section titled “Going live checklist”- Providers you need are enabled on your account, and discovery returns them.
- You pick networks with the right
ramp_typefor each direction. -
provider_config.account_typeis the network’schannel_type. - Every field in
required_fields[provider]is on your requests. - Amounts are sent with at most two decimal places.
- Phone numbers are in international format, e.g.
+256769759910. - Card payments carry a
redirect_url, and your return page waits for the result. - Your callback endpoint answers
200quickly and matches onpayment_config.reference. - You check transaction status before retrying a payment that timed out, so a payer is never charged twice.
Common mistakes
Section titled “Common mistakes”| Symptom | Cause | Fix |
|---|---|---|
| Discovery is empty for every country | No providers assigned to your account | Ask PayHero to enable them (step 2) |
Invalid data provided, or a rejected channel |
account_type sent as phone |
Send the network’s channel_type, e.g. momo |
| Provider rejects the network | network_code sent as the network_id value |
Send each field as discovery gives it |
| A payout used a collection channel | Network picked without checking ramp_type |
Use withdraw networks for payouts, deposit for collections |
missing country in recipient |
No country on the customer |
Send customer.country |
| Amount rejected | Converted amount with many decimals, e.g. 1300.50139 |
Round to two decimal places |
redirect_url is required for a card payment |
Card payment with no return address | Send payment_config.redirect_url |
provider "…" is not a known provider code |
Misspelled or unassigned code | Copy the code from available_providers |

