Skip to content

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.

  1. Get API credentials — a username and password from your dashboard.
  2. Have providers enabled on your account. Without this, discovery returns nothing.
  3. Discover what the payer’s country supports.
  4. Choose a rail, a provider and a network from that answer.
  5. Identify the customer — inline, or by a saved beneficiary.
  6. Create the payment.
  7. Complete it the way the rail requires — a phone prompt, a bank deposit, or a card page.
  8. Confirm the result from the callback.

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.

Terminal window
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.

Check it worked with step 3: if available_providers comes back empty for a country you expect to support, this step is the reason.

Terminal window
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”
  1. Pick a rail whose value in rails is true. It becomes transaction_channel.
  2. Pick a provider code from available_providers[rail]. It becomes provider.
  3. From provider_networks[provider], pick a network whose ramp_type matches the direction — deposit for a collection, withdraw for a payout.
  4. Copy that network’s fields into provider_config, as mapped in How routing works.

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.

Terminal window
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.

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

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.

  • Providers you need are enabled on your account, and discovery returns them.
  • You pick networks with the right ramp_type for each direction.
  • provider_config.account_type is the network’s channel_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 200 quickly and matches on payment_config.reference.
  • You check transaction status before retrying a payment that timed out, so a payer is never charged twice.
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