Developers
Token & API documentation
Integrate Paysecure with checkout, payouts, and webhooks. Create a token from Dashboard → Tokens and start in the sandbox.
On this page
- Getting Started
- SDK & Plugins
- API Libraries (Python & Node.js)
- Fees & Pricing
- Authentication
- Base URL
- API Endpoints
- Payouts
- Getting Transaction Reference
- Step-by-Step Integration
- Checkout Pages
- Payment Intent Checkout
- Direct Payments (Without Checkout)
- Integration Examples
- Webhooks
- Error Handling
- Rate Limiting
Start here
Getting started
Integrate Paysecure with a merchant token. Sandbox first, then live — the same endpoints, different keys.
Global by design
Currencies & payment methods
Paysecure is multi-currency. Your account currency comes from the country you sign up in — every amount you charge, settle, and get paid out is in that currency. Pick a country below and the code samples on this page update to that market's currency and mobile-money provider codes.
Supported countries, currencies & mobile-money providers
| Country | Currency | Provider codes |
|---|---|---|
| Benin (BJ) | XOF | MTN_MOMO_BEN, MOOV_BEN |
| Burkina Faso (BF) | XOF | MOOV_BFA, ORANGE_BFA |
| Cameroon (CM) | XAF | MTN_MOMO_CMR, ORANGE_CMR |
| Côte d'Ivoire (CI) | XOF | MTN_MOMO_CIV, ORANGE_CIV, MOOV_CIV, WAVE_CIV |
| DR Congo (CD) | CDF | MTN_MOMO_COD, ORANGE_COD, AIRTEL_COD, VODACOM_MPESA_COD |
| Congo-Brazzaville (CG) | XAF | MTN_MOMO_COG, AIRTEL_COG |
| Gabon (GA) | XAF | AIRTEL_GAB |
| Ghana (GH) | GHS | MTN_MOMO_GHA, AIRTELTIGO_GHA, VODAFONE_GHA |
| Kenya (KE) | KES | MPESA_KEN, AIRTEL_KEN |
| Malawi (MW) | MWK | AIRTEL_MWI, TNM_MWI |
| Mozambique (MZ) | MZN | VODACOM_MOZ, MOVITEL_MOZ |
| Niger (NE) | XOF | AIRTEL_NER, ORANGE_NER |
| Nigeria (NG) | NGN | MTN_MOMO_NGA, AIRTEL_NGA |
| Rwanda (RW) | RWF | MTN_MOMO_RWA, AIRTEL_RWA |
| Senegal (SN) | XOF | FREE_SEN, ORANGE_SEN, WAVE_SEN |
| Sierra Leone (SL) | SLE | ORANGE_SLE, AIRTEL_SLE |
| Tanzania (TZ) | TZS | AIRTEL_TZA, VODACOM_TZA, TIGO_TZA, HALOTEL_TZA |
| Togo (TG) | XOF | TOGOCOM_TGO, MOOV_TGO |
| Uganda (UG) | UGX | MTN_MOMO_UGA, AIRTEL_OAPI_UGA |
| Zambia (ZM) | ZMW | MTN_MOMO_ZMB, AIRTEL_ZMB, ZAMTEL_ZMB |
Create a token
- Log in and open Tokens in the sidebar.
- Complete your profile if the dashboard asks for it.
- Generate a Sandbox token for testing, or Live after KYC.
- Copy the token immediately — it is shown once. Store it as an environment variable.
Shown once. If you lose a token, generate a new one from Dashboard → Tokens. Do not commit tokens to git.
Sandbox vs live. Sandbox moves no real money. Live requires KYC. Never put a token in client-side JavaScript or a mobile app binary.
Test the connection
Use the test action on the Tokens page, or send an authenticated request to the base URL with your Bearer token. When that succeeds, you are ready for checkout, payouts, or webhooks.
SDK & Plugins
Integrate Paysecure seamlessly with your application using our official SDKs and plugins. These tools are designed to make integration easier and faster.
API Libraries
We provide official libraries for Node.js, Python, and PHP. Install one of them, then run the command below in your terminal.
Click "Copy" next to the command, paste in your terminal, and press Enter. Click "View Docs" to see full documentation.
Node.js Library
Official Node.js library for the Paysecure API.
npm install payhiive-node
Python Library
Official Python library for the Paysecure API.
pip install payhiive-python
PHP Library
Official PHP library for the Paysecure API.
composer require payhiive/payhiive-php
PAYHIIVE Node.js SDK Documentation
Official Node.js library for the Paysecure API
Installation
npm install payhiive-node
Quick Start
const { Payhiive } = require('payhiive-node');
// Initialize the client with your token
const client = new Payhiive({ apiKey: 'your_token_here' });
// Create a payment intent (recommended)
const payment = await client.payments.create({
amount: 50000, // Amount in UGX
description: 'Order #12345',
callbackUrl: 'https://yoursite.com/webhooks/payhiive'
});
// Redirect customer to the checkout URL
console.log(payment.checkout_url);
API Reference
Payments
payments.create()payments.retrieve(id)payments.list()payments.getStatus(id)
Payment Links
paymentLinks.create()paymentLinks.createWithAmounts()paymentLinks.list()paymentLinks.delete(id)
Checkout Sessions
checkout.create()checkout.retrieve(id)checkout.isPaid(id)
const link = await client.paymentLinks.create({
amount: 100000,
currency: 'UGX',
description: 'Membership Fee'
});
console.log(link.data.payment_link);
const session = await client.checkout.create({
amount: 75000,
currency: 'UGX',
successUrl: 'https://site.com/success',
cancelUrl: 'https://site.com/cancel'
});
Supported Providers
MTN_MOMO_UGA
AIRTEL_OAPI_UGA
Download SDK
Get the complete package
PAYHIIVE Python SDK Documentation
Official Python library for the Paysecure API
Installation
pip install payhiive-python
Quick Start
from payhiive import Payhiive
# Initialize the client with your token
client = Payhiive(api_key="your_token_here")
# Create a payment intent (recommended)
payment = client.payments.create(
amount=50000, # Amount in UGX
description="Order #12345",
callback_url="https://yoursite.com/webhooks/payhiive"
)
# Redirect customer to the checkout URL
print(payment["checkout_url"])
API Reference
Payments
payments.create()- Create payment intentpayments.retrieve(id)- Get paymentpayments.list()- List paymentspayments.get_status(id)- Get status
Payment Links
payment_links.create()- Create linkpayment_links.create_with_amounts()- Multi-amountpayment_links.list()- List linkspayment_links.delete(id)- Delete link
Checkout Sessions
checkout.create()- Create sessioncheckout.retrieve(id)- Get sessioncheckout.is_paid(id)- Check paid
More Examples
link = client.payment_links.create(
amount=100000,
currency="UGX",
description="Membership Fee"
)
print(link["data"]["payment_link"])
session = client.checkout.create(
amount=75000,
currency="UGX",
success_url="https://site.com/success",
cancel_url="https://site.com/cancel"
)
Supported Providers
MTN_MOMO_UGA
AIRTEL_OAPI_UGA
Download SDK
Get the complete package
PAYHIIVE PHP SDK Documentation
Official PHP library for the Paysecure API
Installation
composer require payhiive/payhiive-php
Quick Start
<?php
use Payhiive\Payhiive;
// Initialize the client with your token
$client = new Payhiive('your_token_here');
// Create a payment intent (recommended)
$payment = $client->payments->create(
50000, // Amount in UGX
'Order #12345',
'https://yoursite.com/webhooks/payhiive'
);
// Redirect customer to the checkout URL
header('Location: ' . $payment['checkout_url']);
API Reference
Payments
$client->payments->create()$client->payments->retrieve($id)$client->payments->list()$client->payments->getStatus($id)
Payment Links
$client->paymentLinks->create()$client->paymentLinks->createWithAmounts()$client->paymentLinks->list()$client->paymentLinks->delete($id)
Checkout Sessions
$client->checkout->create()$client->checkout->retrieve($id)$client->checkout->isPaid($id)
$link = $client->paymentLinks->create([
'amount' => 100000,
'currency' => 'UGX',
'description' => 'Membership Fee'
]);
echo $link['data']['payment_link'];
$session = $client->checkout->create([
'amount' => 75000,
'currency' => 'UGX',
'success_url' => 'https://site.com/success',
'cancel_url' => 'https://site.com/cancel'
]);
Supported Providers
MTN_MOMO_UGA
AIRTEL_OAPI_UGA
Download SDK
Get the complete package
Available Plugins
WordPress Plugin
Accept mobile money payments on your WordPress site with our official plugin.
Installation Guides
WordPress Plugin Installation
- Download the plugin zip file from above
- Go to WordPress admin panel → Plugins → Add New → Upload Plugin
- Upload the downloaded zip file and click "Install Now"
- After installation, click "Activate Plugin"
- Go to Paysecure in the WordPress menu and enter your token (from Dashboard → Tokens)
- Configure your payment settings and start accepting payments!
Security
Authentication
Every request needs your merchant token. Create it under Dashboard → Tokens. Tokens start with sk_ — that is the only credential the API expects.
Required headers
Authorization
Bearer sk_your_token — sandbox or live, from Dashboard → Tokens.
Content-Type
Send JSON as application/json on POST and PUT bodies.
Example request
Pass the token on every call. Amounts are in major units (USD dollars, not cents).
curl -X POST https://payset.okao.site/api/v1/payments \
-H "Authorization: Bearer sk_your_token" \
-H "Content-Type: application/json" \
-d '{
"amount": 100,
"currency": "USD",
"description": "Payment for order #123"
}'
Keep tokens on the server. Never put a live or sandbox token in frontend JavaScript, a mobile app binary, or a public repo.
Pricing
Fees & pricing
Platform fees sit on top of the amount you collect. You receive the exact USD you requested; the customer pays that amount plus the fee.
Payment links
Payouts
API example — collect $100.00
Payment link example — collect $100.00
Payout example — send $100.00
Payout fees are deducted from the withdrawal, so the recipient gets the net amount.
Fees on top. To collect $100.00 via API or a payment link, the customer pays $100.00 plus the platform fee. Your balance is credited $100.00.
This install
Base URL
All API paths on this page are relative to the domain you are viewing. Point APP_URL in .env (or App URL in Admin → Settings) at the same host so checkout links and emails match.
https://payset.okao.site/api/v1
Use HTTPS in production. Example snippets below always use this installation’s origin — they are not tied to a vendor domain.
API Endpoints
Two ways to accept a payment — pick whichever fits your integration:
- Hosted checkout (
POST /payments): sendamount,description,callback_url→ you get back acheckout_urland redirect the customer to it. We handle the phone number entry and network selection UI for you. See Checkout Pages. - Direct charge, no redirect (
POST /charges): you collect the customer's phone number and mobile money network yourself, on your own page. We send the payment prompt straight to their phone; they never leave your site.
Neither is required — use one, the other, or both for different parts of your product. This section documents /charges (direct, no checkout). For the hosted checkout flow, jump to Checkout Pages or Payment Intent Checkout.
Payments
Create a Direct Charge (No Checkout Page)
Charge a mobile money number directly — no redirect, no checkout page. You collect the phone number and provider on your own form.
POST /api/v1/payments/direct is kept as a backward-compatible alias — new integrations should use /charges.
Request Body
| Parameter | Type | Description | Required |
|---|---|---|---|
amount |
integer | Amount in UGX (e.g., 10000 = 10,000 UGX) | Required |
currency |
string | Currency code (UGX) | Required |
phone_number |
string | Customer mobile money phone number (e.g., 256700000000) | Required |
provider |
string | Mobile money provider: MTN_MOMO_UGA or AIRTEL_OAPI_UGA |
Required |
description |
string | Payment description | Optional |
callback_url |
string | URL we POST to when the charge's status changes (see Webhooks) | Optional |
metadata |
object | Additional metadata (key-value pairs) | Optional |
Example Request
POST https://payset.okao.site/api/v1/charges
Headers:
Authorization: Bearer sk_your_token
Content-Type: application/json
Body:
{
"amount": 10000,
"currency": "UGX",
"phone_number": "256700000000",
"provider": "MTN_MOMO_UGA",
"description": "Payment for order #123",
"metadata": {
"order_id": "123",
"product_id": "456"
}
}
Example Response (Success)
{
"success": true,
"data": {
"id": 123,
"transaction_id": "TXN-ABC123XYZ",
"deposit_id": "DEP-123456789",
"amount": 10000,
"currency": "UGX",
"status": "pending",
"provider": "payment_provider",
"message": "Payment request sent successfully",
"created_at": "2025-12-10T12:00:00Z"
}
}
Example Response (Error)
{
"success": false,
"message": "Provider MTN_MOMO_UGA is not enabled. Please contact support to enable this provider.",
"error_code": "PROVIDER_NOT_ENABLED"
}
Note: The transaction status will be pending initially. It will be updated to completed or failed via webhook or when you check the status. The net_amount and fee are calculated and will be reflected in your account balance once the transaction is completed.
Transaction Status Values
| Status | Description |
|---|---|
pending |
Payment request sent, waiting for customer to approve |
processing |
Customer has approved, payment is being processed |
completed |
Payment successfully completed and funds credited |
failed |
Payment failed (customer declined, insufficient funds, etc.) |
cancelled |
Payment was cancelled |
Get Payment Status
Retrieve the current status and details of a specific payment transaction. This endpoint works for any transaction regardless of how it was created — direct charge (/charges) or hosted checkout (/payments).
Response Example
{
"success": true,
"data": {
"id": 123,
"transaction_id": "TXN-ABC123XYZ",
"reference": "b6a677d8-8a3d-4d2d-9de5-d439510d7c62",
"amount": 10000,
"currency": "UGX",
"status": "completed",
"redirect_url": "https://your-website.com/success",
"created_at": "2025-12-10T12:00:00Z",
"updated_at": "2025-12-10T12:05:00Z"
}
}
Getting Transaction Reference
The transaction reference is a unique identifier (UUID format) that is returned in the response when you create a payment or retrieve payment status. This reference can be used for tracking, reconciliation, and webhook verification.
To get the transaction reference:
- From Payment Response: When you create a payment using
POST /payments, thereferenceis included in the response. - From Status Check: When you retrieve payment status using
GET /payments/{transaction_id}, thereferencefield is included in the response data. - From Dashboard: You can also view the transaction reference in your merchant dashboard at
https://payset.okao.site/customer/dashboard/recent-transactions.
Note: The transaction reference is different from the transaction_id. The reference is typically a UUID format (e.g., b6a677d8-8a3d-4d2d-9de5-d439510d7c62) and is used for provider-specific tracking, while the transaction_id is the internal transaction identifier used when calling the API with your token.
Step-by-Step: Integrating Transaction Reference in Your System
This guide will walk you through integrating transaction reference tracking into your application system.
Step 1: Set Up Your Token
- Log in to your merchant dashboard at
https://payset.okao.site/customer/login - Navigate to Dashboard → Tokens
- Generate a new token (Dashboard → Tokens)
- Store your token and public identifier securely in your application's environment variables (e.g. in .env):
PAYHIIVE_TOKEN=sk_your_token API_BASE_URL=https://payset.okao.site/api/v1
Step 2: Create a Payment Request
When creating a payment, make a POST request to the payments endpoint:
POST https://payset.okao.site/api/v1/payments
Headers:
Authorization: Bearer sk_your_token
Content-Type: application/json
Body:
{
"amount": 10000,
"currency": "UGX",
"phone_number": "256700000000",
"provider": "MTN_MOMO_UGA",
"description": "Payment for order #123"
}
The response will include a transaction_id:
{
"success": true,
"data": {
"id": 123,
"transaction_id": "TXN-ABC123XYZ",
"amount": 10000,
"currency": "UGX",
"status": "pending",
"created_at": "2025-12-10T12:00:00Z"
}
}
Step 3: Retrieve the Transaction Reference
After creating a payment, use the transaction_id to retrieve the full transaction details, including the reference:
GET https://payset.okao.site/api/v1/payments/{transaction_id}
Headers:
Authorization: Bearer sk_your_token
Content-Type: application/json
The response will include the reference field:
{
"success": true,
"data": {
"id": 123,
"transaction_id": "TXN-ABC123XYZ",
"reference": "b6a677d8-8a3d-4d2d-9de5-d439510d7c62",
"amount": 10000,
"currency": "UGX",
"status": "completed",
"created_at": "2025-12-10T12:00:00Z",
"updated_at": "2025-12-10T12:05:00Z"
}
}
Step 4: Store the Reference in Your Database
Save both the transaction_id and reference in your database for future reference and reconciliation:
// Example: Store transaction in your database
INSERT INTO orders (
order_id,
payment_transaction_id,
payment_reference,
amount,
status,
created_at
) VALUES (
'ORDER-12345',
'TXN-ABC123XYZ',
'b6a677d8-8a3d-4d2d-9de5-d439510d7c62',
10000,
'pending',
NOW()
);
Step 5: Check Payment Status Using Reference
You can verify payment status by checking the transaction using either the transaction_id or by querying your database using the stored reference:
// Check status via API
GET https://payset.okao.site/api/v1/payments/TXN-ABC123XYZ
// Or query your database
SELECT * FROM orders
WHERE payment_reference = 'b6a677d8-8a3d-4d2d-9de5-d439510d7c62';
Step 6: Complete Code Example (PHP)
<?php
// Configuration
$apiBaseUrl = 'https://payset.okao.site/api/v1';
$token = 'sk_your_token';
// Step 1: Create Payment
function createPayment($amount, $phoneNumber, $provider, $description) {
global $apiBaseUrl, $token;
$data = [
'amount' => $amount,
'currency' => 'UGX',
'phone_number' => $phoneNumber,
'provider' => $provider,
'description' => $description
];
$ch = curl_init($apiBaseUrl . '/payments');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return json_decode($response, true);
}
// Step 2: Get Transaction Reference
function getTransactionReference($transactionId) {
global $apiBaseUrl, $token;
$ch = curl_init($apiBaseUrl . '/payments/' . $transactionId);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if ($data['success'] && isset($data['data']['reference'])) {
return $data['data']['reference'];
}
return null;
}
// Usage Example
$payment = createPayment(10000, '256700000000', 'MTN_MOMO_UGA', 'Order #12345');
if ($payment['success']) {
$transactionId = $payment['data']['transaction_id'];
// Get the transaction reference
$reference = getTransactionReference($transactionId);
// Store in your database
// INSERT INTO orders (order_id, transaction_id, reference, status)
// VALUES ('ORDER-12345', $transactionId, $reference, 'pending');
echo "Transaction ID: " . $transactionId . "\n";
echo "Reference: " . $reference . "\n";
}
?>
Step 7: Complete Code Example (Node.js/JavaScript)
// Configuration
const API_BASE_URL = 'https://payset.okao.site/api/v1';
const TOKEN = 'sk_your_token';
// Step 1: Create Payment
async function createPayment(amount, phoneNumber, provider, description) {
const response = await fetch(`${API_BASE_URL}/payments`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: amount,
currency: 'UGX',
phone_number: phoneNumber,
provider: provider,
description: description
})
});
return await response.json();
}
// Step 2: Get Transaction Reference
async function getTransactionReference(transactionId) {
const response = await fetch(`${API_BASE_URL}/payments/${transactionId}`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.success && data.data.reference) {
return data.data.reference;
}
return null;
}
// Usage Example
async function processPayment() {
const payment = await createPayment(
10000,
'256700000000',
'MTN_MOMO_UGA',
'Order #12345'
);
if (payment.success) {
const transactionId = payment.data.transaction_id;
const reference = await getTransactionReference(transactionId);
// Store in your database
// await db.query(
// 'INSERT INTO orders (order_id, transaction_id, reference, status) VALUES (?, ?, ?, ?)',
// ['ORDER-12345', transactionId, reference, 'pending']
// );
console.log('Transaction ID:', transactionId);
console.log('Reference:', reference);
}
}
processPayment();
Step 8: Verify Reference in Dashboard
You can also verify the transaction reference by viewing it in your merchant dashboard:
- Log in to your dashboard at
https://payset.okao.site/customer/login - Navigate to Dashboard → Recent Transactions
- Find your transaction and check the Reference column
- The reference will be displayed in UUID format (e.g.,
b6a677d8-8a3d-4d2d-9de5-d439510d7c62)
Best Practices:
- Always store both
transaction_idandreferencein your database - Use the
referencefor reconciliation with payment provider records - Use the
transaction_idfor status checks and when calling the API (with your token) - Display the reference to customers for transaction tracking and support inquiries
- Keep your token secure and never expose it in client-side code
List Payments
Retrieve a list of all your payments. Supports pagination and filtering.
Payouts
Payouts let you send money from your Paysecure balance directly to a recipient's mobile money account — from your own website, app, or backend system. All you need is your secret token from Dashboard → Tokens.
How payouts work
- Your account accumulates a balance from payments your customers make.
- You call
POST /api/v1/payoutswith your secret token. - Paysecure deducts the amount (plus any fee) from your balance and forwards the net to the recipient.
- In sandbox mode, the payout completes instantly with no real money moved.
- In live mode, the payout is submitted to the payment provider and returns
processing; pollGET /payouts/{payout_id}for the final status.
Sandbox vs Live
| Feature | Sandbox | Live |
|---|---|---|
| Token prefix | sk_ (sandbox key) | sk_ (live key) |
| Real money moved | No | Yes |
| KYC required | No | Yes — must be approved |
| Real balance required | No (simulated) | Yes |
| Payout status on success | completed immediately | processing → poll for final |
| Minimum amount enforced | No | Yes |
| SMS sent to recipient | No | Yes |
- Your KYC is submitted and approved.
- Your account has a real balance from completed payments.
- You are using a Live token from Dashboard → Tokens (not sandbox).
- The payment provider is configured and enabled by the platform admin.
Create a Payout
Sends money from your Paysecure balance to a recipient's mobile money number. Supports two payload formats — flat fields or a nested recipient object.
Request Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer sk_your_secret_token | Required |
Content-Type | application/json | Required |
Accept | application/json | Recommended |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
amount |
decimal | Required | The total amount to deduct from your balance (fees are taken from this). E.g. 10000 |
currency |
string (3 chars) | Required | ISO 4217 currency code. E.g. UGX, KES, GHS |
phone_number |
string | Required | Recipient's mobile money number in international format without +. E.g. 256781234567. Alternatively use recipient.accountDetails.phoneNumber. |
provider |
string | Required | Mobile money provider code. E.g. MTN, AIRTEL. Alternatively use recipient.accountDetails.provider. |
payout_id |
string (UUID v4) | Optional | Your own unique UUID for idempotency. Generate and store it before calling the API so you can reconcile even if the network times out. E.g. afb57b93-7849-49aa-babb-4c3ccbfe3d79 |
notes |
string | Optional | A short description stored with the payout. E.g. Salary – March 2025 |
metadata |
object | Optional | Any extra key-value pairs you want stored with the payout record. E.g. {"employee_id": "EMP-001"} |
recipient |
object | Optional | Alternative to flat fields. Object with type: "MMO" and accountDetails: { phoneNumber, provider }. |
Example — Simple (flat fields)
POST https://payset.okao.site/api/v1/payouts
Authorization: Bearer sk_your_secret_token
Content-Type: application/json
Accept: application/json
{
"amount": 10000,
"currency": "UGX",
"phone_number": "256781234567",
"provider": "MTN",
"notes": "March salary"
}
Example — With recipient object & custom payout_id
POST https://payset.okao.site/api/v1/payouts
Authorization: Bearer sk_your_secret_token
Content-Type: application/json
Accept: application/json
{
"payout_id": "afb57b93-7849-49aa-babb-4c3ccbfe3d79",
"amount": 10000,
"currency": "UGX",
"notes": "Salary payout",
"metadata": { "employee_id": "EMP-001", "department": "Sales" },
"recipient": {
"type": "MMO",
"accountDetails": {
"phoneNumber": "256781234567",
"provider": "MTN"
}
}
}
Success Response — Sandbox (201 Created)
In sandbox mode the payout completes instantly. No real money is moved and no SMS is sent.
{
"success": true,
"message": "Payout processed successfully in sandbox mode (no real payout or SMS was sent)",
"data": {
"id": 42,
"payout_id": "afb57b93-7849-49aa-babb-4c3ccbfe3d79",
"amount": 10000,
"currency": "UGX",
"net_amount": 10000,
"fee": 0,
"status": "completed",
"provider": "MTN",
"sandbox_mode": true
}
}
Success Response — Live (201 Created)
In live mode the payout is submitted to the payment provider. Status will be processing. Poll GET /api/v1/payouts/{payout_id} until status becomes completed or failed.
{
"success": true,
"data": {
"id": 43,
"payout_id": "afb57b93-7849-49aa-babb-4c3ccbfe3d79",
"amount": 10000,
"currency": "UGX",
"net_amount": 9800,
"fee": 200,
"status": "processing",
"message": "Payout initiated successfully",
"created_at": "2025-03-29T10:00:00+00:00"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
id | integer | Internal payout record ID. |
payout_id | string (UUID) | The UUID used to track this payout. Use this in GET /payouts/{payout_id}. |
amount | decimal | The amount you requested to send. |
currency | string | Currency code. |
fee | decimal | Fee deducted from your balance on top of the amount. |
net_amount | decimal | Amount the recipient actually receives (amount minus fee). |
status | string | pending → processing → completed or failed. |
sandbox_mode | boolean | Present and true only on sandbox responses. |
created_at | ISO 8601 | Timestamp of when the payout was created. |
Payout Status Lifecycle
| Status | Meaning | Action required |
|---|---|---|
pending | Created but not yet sent to provider. | Poll for update. |
processing | Submitted to payment provider, awaiting confirmation. | Poll GET /payouts/{payout_id}. |
completed | Recipient received the funds. | None — done. |
failed | Provider rejected or could not complete payout. Balance is restored. | Check rejection_reason and retry if appropriate. |
Get Payout Status
Returns the current status of a payout. For pending or processing payouts the API automatically refreshes the status from the payment provider before responding.
Example
GET https://payset.okao.site/api/v1/payouts/afb57b93-7849-49aa-babb-4c3ccbfe3d79
Authorization: Bearer sk_your_secret_token
Accept: application/json
Response
{
"success": true,
"data": {
"id": 43,
"payout_id": "afb57b93-7849-49aa-babb-4c3ccbfe3d79",
"amount": 10000,
"currency": "UGX",
"net_amount": 9800,
"fee": 200,
"status": "completed",
"created_at": "2025-03-29T10:00:00+00:00",
"updated_at": "2025-03-29T10:02:15+00:00"
}
}
List Payouts
Returns a paginated list of your payouts, newest first.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status: pending, processing, completed, failed. |
from_date | date | Filter payouts from this date (inclusive). Format: YYYY-MM-DD. |
to_date | date | Filter payouts up to this date (inclusive). Format: YYYY-MM-DD. |
per_page | integer | Results per page. Default: 20. Max: 100. |
Example
GET https://payset.okao.site/api/v1/payouts?status=completed&from_date=2025-03-01&to_date=2025-03-31&per_page=50
Authorization: Bearer sk_your_secret_token
Accept: application/json
Response
{
"success": true,
"data": [
{
"id": 43,
"payout_id": "afb57b93-7849-49aa-babb-4c3ccbfe3d79",
"amount": 10000,
"currency": "UGX",
"net_amount": 9800,
"fee": 200,
"status": "completed",
"created_at": "2025-03-29T10:00:00+00:00",
"updated_at": "2025-03-29T10:02:15+00:00"
}
],
"pagination": {
"current_page": 1,
"per_page": 50,
"total": 1,
"last_page": 1
}
}
Error Responses
| HTTP | error_code | Meaning & fix |
|---|---|---|
| 401 | UNAUTHORIZED | Missing or invalid token. Check your Authorization header. |
| 403 | KYC_NOT_APPROVED | Live payouts require an approved KYC. Submit KYC from your dashboard. |
| 400 | INSUFFICIENT_BALANCE | Your balance is below the requested amount. Response includes available_balance. |
| 400 | AMOUNT_BELOW_MINIMUM | Net amount after fees is below platform minimum. Retry with minimum_request_amount from the response. |
| 400 | PROVIDER_NOT_ENABLED | The provider you specified is not active. Contact support. |
| 400 | PAYOUTS_NOT_ALLOWED / PAYOUT_REJECTED | Payment provider rejected the payout. Your balance was not deducted. Check provider configuration. |
| 503 | PROVIDER_NOT_CONFIGURED | Platform payment provider is not set up. Contact the platform admin. |
| 422 | — | Validation error. The errors object contains field-level messages. |
Insufficient Balance Error Example
{
"success": false,
"message": "Insufficient balance. Your available balance is 3,000.00 UGX. This payout requires 10,000.00 UGX.",
"error_code": "INSUFFICIENT_BALANCE",
"available_balance": 3000,
"required_amount": 10000,
"currency": "UGX"
}
Amount Below Minimum Error Example
{
"success": false,
"message": "After fees, the amount to be sent would be 588 UGX. Minimum payout per transaction is 6,000 UGX. Request at least 6,122 UGX so that the amount sent is at least 6,000 UGX.",
"error_code": "AMOUNT_BELOW_MINIMUM",
"net_amount_after_fees": 588,
"minimum_net_payout": 6000,
"minimum_request_amount": 6122
}
Code Examples
cURL
curl -X POST https://payset.okao.site/api/v1/payouts \
-H "Authorization: Bearer sk_your_secret_token" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"amount": 10000,
"currency": "UGX",
"phone_number": "256781234567",
"provider": "MTN",
"notes": "March salary"
}'
JavaScript (fetch)
const response = await fetch('https://payset.okao.site/api/v1/payouts', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_your_secret_token',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
amount: 10000,
currency: 'UGX',
phone_number: '256781234567',
provider: 'MTN',
notes: 'March salary',
}),
});
const data = await response.json();
if (data.success) {
const { payout_id, status, net_amount } = data.data;
console.log(`Payout ${payout_id} is ${status}. Recipient gets ${net_amount} UGX.`);
// For live mode: poll until completed or failed
if (status === 'processing') {
pollPayoutStatus(payout_id);
}
} else {
console.error(data.error_code, data.message);
}
async function pollPayoutStatus(payoutId) {
const res = await fetch(`https://payset.okao.site/api/v1/payouts/${payoutId}`, {
headers: { 'Authorization': 'Bearer sk_your_secret_token' },
});
const result = await res.json();
console.log('Status:', result.data.status);
}
PHP (cURL)
<?php
$token = 'sk_your_secret_token';
$baseUrl = 'https://payset.okao.site';
$payload = json_encode([
'amount' => 10000,
'currency' => 'UGX',
'phone_number' => '256781234567',
'provider' => 'MTN',
'notes' => 'March salary',
]);
$ch = curl_init("$baseUrl/api/v1/payouts");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $token",
'Content-Type: application/json',
'Accept: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($response['success']) {
$payoutId = $response['data']['payout_id'];
$status = $response['data']['status'];
echo "Payout $payoutId — Status: $status\n";
} else {
echo "Error [{$response['error_code']}]: {$response['message']}\n";
}
Python (requests)
import requests
TOKEN = 'sk_your_secret_token'
BASE_URL = 'https://payset.okao.site'
headers = {
'Authorization': f'Bearer {TOKEN}',
'Content-Type': 'application/json',
'Accept': 'application/json',
}
payload = {
'amount': 10000,
'currency': 'UGX',
'phone_number': '256781234567',
'provider': 'MTN',
'notes': 'March salary',
}
response = requests.post(f'{BASE_URL}/api/v1/payouts', json=payload, headers=headers)
data = response.json()
if data['success']:
payout_id = data['data']['payout_id']
status = data['data']['status']
print(f'Payout {payout_id} — {status}')
# Poll for final status if processing
if status == 'processing':
check = requests.get(f'{BASE_URL}/api/v1/payouts/{payout_id}', headers=headers)
print('Final status:', check.json()['data']['status'])
else:
print(f"Error [{data['error_code']}]: {data['message']}")
Best practices
- Generate and store your own
payout_id(UUID v4) before calling the API. After a timeout you can still look it up by that ID. - Keep
sk_tokens on the server. Never put them in frontend JavaScript or a mobile app binary. - Develop in sandbox — no real money moves and payouts complete immediately.
- In live mode, poll
GET /payouts/{payout_id}until status iscompletedorfailed. - Handle
INSUFFICIENT_BALANCEusing theavailable_balancefield from the response. - On
AMOUNT_BELOW_MINIMUM, retry with at leastminimum_request_amount.
Refunds
Create Refund
Create a refund for a payment.
Request Body
| Parameter | Type | Description | Required |
|---|---|---|---|
payment_id |
string | ID of the payment to refund | Required |
amount |
integer | Refund amount in cents (optional, defaults to full amount) | Optional |
reason |
string | Reason for refund | Optional |
Checkout Pages (Using Your Token)
Paysecure provides hosted checkout pages that you can redirect your customers to for secure payment processing. Authenticate with your token (Dashboard → Tokens) when creating payment intents. This is the easiest way to accept payments without handling sensitive payment data yourself.
Creating a Payment Intent
To create a payment intent, make a POST request to the payments endpoint with the payment details.
Create Payment Intent
Create a new payment intent and get a secure checkout URL to redirect your customer to.
Note: Currency is fixed to UGX for payment intents. The amount should be provided as a decimal number (e.g., 50000.00 for 50,000 UGX).
Request Body
| Parameter | Type | Description | Required |
|---|---|---|---|
amount |
decimal | Payment amount (e.g., 50000.00 for 50,000 UGX) | Required |
description |
string | Payment description (max 500 characters) | Optional |
callback_url |
string | Webhook URL to receive payment status updates | Optional |
Example Request
POST https://payset.okao.site/api/v1/payments
Headers:
Authorization: Bearer sk_your_token
Content-Type: application/json
Body:
{
"amount": 50000.00,
"description": "Payment for Order #12345",
"callback_url": "https://yoursite.com/webhooks/payment-status"
}
Example Response
{
"reference": "PHV_72UJVK2EN8",
"checkout_url": "https://payset.okao.site/checkout/PHV_72UJVK2EN8"
}
Redirecting to Checkout
After creating a payment intent, redirect your customer to the checkout_url provided in the response.
Important: Always use the checkout_url from the response. Never construct the URL manually or allow the frontend to modify the amount or payment details.
JavaScript Example
// Create payment intent
const response = await fetch('https://payset.okao.site/api/v1/payments', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_your_token',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 50000.00, // Amount as decimal (50,000 UGX)
description: 'Payment for Order #12345',
callback_url: 'https://yoursite.com/webhooks/payment-status'
})
});
const data = await response.json();
if (data.reference && data.checkout_url) {
// Redirect customer to secure checkout page
window.location.href = data.checkout_url;
} else {
console.error('Failed to create payment intent:', data);
}
PHP Example
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://payset.okao.site/api/v1/payments');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'amount' => 50000.00, // Amount as decimal (50,000 UGX)
'description' => 'Payment for Order #12345',
'callback_url' => 'https://yoursite.com/webhooks/payment-status'
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer sk_your_token',
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
if ($httpCode === 201 && isset($data['checkout_url'])) {
// Redirect customer to secure checkout page
header('Location: ' . $data['checkout_url']);
exit;
} else {
// Handle error
echo 'Payment failed: ' . ($data['message'] ?? 'Unknown error');
}
?>
Checking Payment Status
After redirecting your customer to the checkout page, you can check the payment status using the status endpoint.
Get Payment Status
Check the status of a payment intent using the reference returned when creating the payment.
Example Request
GET https://payset.okao.site/checkout/PHV_72UJVK2EN8/status
Headers:
Accept: application/json
Example Response
{
"success": true,
"data": {
"reference": "PHV_72UJVK2EN8",
"status": "paid",
"transaction_status": "completed",
"transaction_id": "TXN-ABCDEFGHIJKL",
"is_paid": true,
"is_completed": true
}
}
Automatic Payment Provider Check: The status endpoint automatically queries the payment provider's API directly if the payment is still pending. This ensures you get real-time status updates even if webhooks are delayed.
Payment Intent Statuses
| Status | Description |
|---|---|
pending |
Payment intent created, waiting for customer to pay |
paid |
Payment successfully completed |
failed |
Payment failed |
expired |
Payment intent expired (default: 24 hours) |
Checkout Page Features
- Secure Payment Processing: All payment data is handled securely by Paysecure with SSL encryption
- Mobile Money Payments: Supports MTN Mobile Money and Airtel Money
- Mobile Optimized: Fully responsive design optimized for mobile devices
- Real-time Status Updates: Automatic status polling to track payment progress
- Expiration Management: Payment intents expire after 24 hours for security
- Email Notifications: Merchants receive email notifications when payments are completed
- Webhook Support: Instant payment notifications via webhooks
Best Practice: Always verify the payment status on your server using the reference and status endpoint, rather than relying solely on the redirect. This ensures the payment was actually completed.
Hosted checkout
Payment intent checkout
Redirect customers to a Paysecure checkout page. Amount, currency, and recipient stay on the server — the frontend only ever sees an unguessable reference.
Copy & paste
Quick start
Create a payment with your token, then send the customer to the checkout_url we return.
https://payset.okao.site/checkout/PHV_72UJVK2EN8
Example only. Swap PHV_72UJVK2EN8 for the reference in the API response.
const API_BASE_URL = 'https://payset.okao.site/api/v1';
const TOKEN = 'sk_your_token';
async function redirectToPayhiiveCheckout(amount, description) {
const response = await fetch(`${API_BASE_URL}/payments`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: amount, // e.g. 100.00 USD
currency: 'USD',
description: description,
callback_url: 'https://yoursite.com/webhooks/payment'
})
});
const data = await response.json();
if (!data.checkout_url) throw new Error(data.message || 'Failed to create payment');
window.location.href = data.checkout_url;
}
document.getElementById('pay-button').addEventListener('click', () => {
redirectToPayhiiveCheckout(100.00, 'Payment for Order #12345');
});
<?php
$API_BASE_URL = 'https://payset.okao.site/api/v1';
$TOKEN = 'sk_your_token';
function redirectToPayhiiveCheckout($amount, $description) {
global $API_BASE_URL, $TOKEN;
$ch = curl_init($API_BASE_URL . '/payments');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $TOKEN,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => $amount, // e.g. 100.00 USD
'currency' => 'USD',
'description' => $description,
'callback_url' => 'https://yoursite.com/webhooks/payment',
]),
]);
$data = json_decode(curl_exec($ch), true);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 201 && isset($data['checkout_url'])) {
header('Location: ' . $data['checkout_url']);
exit;
}
throw new RuntimeException($data['message'] ?? 'Payment failed');
}
if (isset($_POST['pay_now'])) {
redirectToPayhiiveCheckout(100.00, 'Payment for Order #12345');
}
import requests
from flask import redirect
API_BASE_URL = 'https://payset.okao.site/api/v1'
TOKEN = 'sk_your_token'
def redirect_to_payhiive_checkout(amount, description):
response = requests.post(
f'{API_BASE_URL}/payments',
headers={
'Authorization': f'Bearer {TOKEN}',
'Content-Type': 'application/json',
},
json={
'amount': amount, # e.g. 100.00 USD
'currency': 'USD',
'description': description,
'callback_url': 'https://yoursite.com/webhooks/payment',
},
)
result = response.json()
if response.status_code == 201 and 'checkout_url' in result:
return redirect(result['checkout_url'])
return {'error': result.get('message', 'Payment failed')}
@app.route('/pay', methods=['POST'])
def pay():
return redirect_to_payhiive_checkout(100.00, 'Payment for Order #12345')
That’s the whole loop. Create the payment on your server, redirect to checkout_url, and let the hosted page collect the customer’s payment method.
Why hosted checkout: references are unguessable (PHV_…), amount and currency are never taken from the browser, intents expire (default 24 hours), and your token never leaves the server.
Step by step
Same flow, broken into the four API moments.
1. Create a payment intent
POST to /api/v1/payments with the amount and description.
POST https://payset.okao.site/api/v1/payments
Authorization: Bearer sk_your_token
Content-Type: application/json
{
"amount": 100.00,
"currency": "USD",
"description": "Payment for Order #12345"
}
2. Read the checkout URL
A 201 response includes reference and checkout_url.
{
"reference": "PHV_72UJVK2EN8",
"checkout_url": "https://payset.okao.site/checkout/PHV_72UJVK2EN8"
}
3. Redirect the customer
Send them to the checkout_url from the response. Do not build that URL yourself or trust a frontend-supplied amount.
4. Confirm completion
Use a callback_url webhook when status changes, or poll the intent with the reference.
Creating a Payment Intent
To create a payment intent, make a POST request to the payments endpoint. This will create a payment intent and return a checkout URL.
Create Payment Intent
Create a new payment intent and get a secure checkout URL to redirect your customer to.
Request Body
| Parameter | Type | Description | Required |
|---|---|---|---|
amount |
decimal | Payment amount (e.g., 50000.00 for 50,000 UGX) | Required |
description |
string | Payment description (max 500 characters) | Optional |
callback_url |
string | Webhook URL to receive payment status updates | Optional |
Note: Currency is fixed to UGX for payment intents. The amount should be provided as a decimal number (e.g., 50000.00 for 50,000 UGX).
Example Request
POST https://payset.okao.site/api/v1/payments
Headers:
Authorization: Bearer sk_your_token
Content-Type: application/json
Body:
{
"amount": 50000.00,
"description": "Payment for Order #12345",
"callback_url": "https://yoursite.com/webhooks/payment-status"
}
Example Response
{
"reference": "PHV_72UJVK2EN8",
"checkout_url": "https://payset.okao.site/checkout/PHV_72UJVK2EN8"
}
How to Redirect Customers to Checkout
After creating a payment intent, you'll receive a checkout_url in the response. Redirect your customer to this URL to complete the payment.
Important: Always use the checkout_url from the response. Never construct the URL manually or allow the frontend to modify the amount or payment details.
What Customers Will See
When customers visit the checkout URL, they will see:
- PayHive Branding: Professional, secure checkout page
- Payment Amount: Displayed prominently (read-only, from database)
- Merchant Name: Your business name
- Payment Description: Description you provided when creating the payment intent
- Mobile Money Options: MTN Mobile Money and Airtel Money buttons
- Secure Form: Fields for customer name, email, and phone number
Complete Integration Examples
JavaScript/React Example (Frontend)
Note: This example shows frontend code, but in production, you should make the API call from your backend server to keep your token secure.
// Step 1: Create payment intent (call from your backend)
async function createPaymentIntent(amount, description) {
try {
const response = await fetch('https://payset.okao.site/api/v1/payments', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk_your_token', // ⚠️ Keep token on backend!
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: amount,
description: description,
callback_url: 'https://yoursite.com/webhooks/payment-status' // Optional
})
});
const data = await response.json();
// Step 2: Check if payment intent was created successfully
if (data.reference && data.checkout_url) {
// Step 3: Redirect customer to checkout page
window.location.href = data.checkout_url;
return { success: true, reference: data.reference };
} else {
console.error('Failed to create payment intent:', data);
return { success: false, error: data.message || 'Unknown error' };
}
} catch (error) {
console.error('Error creating payment intent:', error);
return { success: false, error: error.message };
}
}
// Usage example:
// When customer clicks "Pay Now" button
document.getElementById('pay-button').addEventListener('click', async () => {
const result = await createPaymentIntent(50000.00, 'Payment for Order #12345');
if (!result.success) {
alert('Failed to create payment: ' + result.error);
}
// Customer will be automatically redirected to checkout page
});
PHP Example (Backend - Recommended)
<?php
/**
* Step 1: Create Payment Intent
* This should be called from your backend server (e.g., when customer clicks "Pay Now")
*/
function createPaymentIntent($amount, $description, $callbackUrl = null) {
$baseUrl = 'https://payset.okao.site/api/v1';
$token = 'sk_your_token'; // ⚠️ Keep token secure on your server!
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '/payments');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
$payload = [
'amount' => $amount,
'description' => $description
];
if ($callbackUrl) {
$payload['callback_url'] = $callbackUrl;
}
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($response, true);
// Step 2: Check response and get checkout URL
if ($httpCode === 201 && isset($data['checkout_url'])) {
return [
'success' => true,
'reference' => $data['reference'],
'checkout_url' => $data['checkout_url']
];
} else {
return [
'success' => false,
'error' => $data['message'] ?? 'Unknown error'
];
}
}
// Example: Laravel Controller
class PaymentController extends Controller {
public function initiateCheckout(Request $request) {
// Validate request
$request->validate([
'amount' => 'required|numeric|min:0.01',
'description' => 'nullable|string|max:500'
]);
// Step 1: Create payment intent
$result = createPaymentIntent(
$request->amount,
$request->description ?? 'Payment',
route('webhooks.payment-status') // Your webhook URL
);
// Step 2: Check if successful
if ($result['success']) {
// Step 3: Redirect customer to checkout page
return redirect($result['checkout_url']);
} else {
// Handle error
return back()->withErrors(['error' => $result['error']]);
}
}
}
// Example: Plain PHP (when customer clicks "Pay Now")
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['amount'])) {
$result = createPaymentIntent(
floatval($_POST['amount']),
$_POST['description'] ?? 'Payment'
);
if ($result['success']) {
// Step 3: Redirect customer to checkout
header('Location: ' . $result['checkout_url']);
exit;
} else {
// Show error to user
die('Payment failed: ' . $result['error']);
}
}
?>
Python Example (Flask/Django)
import requests
from flask import redirect, request, jsonify # For Flask
# from django.shortcuts import redirect # For Django
def create_payment_intent(amount, description, callback_url=None):
"""
Step 1: Create a payment intent via API
Returns: dict with 'success', 'checkout_url', and 'reference'
"""
url = 'https://payset.okao.site/api/v1/payments'
headers = {
'Authorization': 'Bearer sk_your_token', # ⚠️ Keep token on backend!
'Content-Type': 'application/json'
}
data = {
'amount': amount,
'description': description
}
if callback_url:
data['callback_url'] = callback_url
try:
response = requests.post(url, json=data, headers=headers)
result = response.json()
# Step 2: Check if payment intent was created
if response.status_code == 201 and 'checkout_url' in result:
return {
'success': True,
'checkout_url': result['checkout_url'],
'reference': result['reference']
}
else:
return {
'success': False,
'error': result.get('message', 'Unknown error')
}
except Exception as e:
return {
'success': False,
'error': str(e)
}
# Flask Example
@app.route('/checkout/initiate', methods=['POST'])
def initiate_checkout():
amount = request.json.get('amount')
description = request.json.get('description', 'Payment')
# Step 1: Create payment intent
result = create_payment_intent(
amount=amount,
description=description,
callback_url='https://yoursite.com/webhooks/payment-status'
)
if result['success']:
# Step 2: Redirect customer to checkout page
return redirect(result['checkout_url'])
else:
return jsonify({'error': result['error']}), 400
# Django Example
from django.shortcuts import redirect
from django.http import JsonResponse
def initiate_checkout(request):
if request.method == 'POST':
amount = request.POST.get('amount')
description = request.POST.get('description', 'Payment')
# Step 1: Create payment intent
result = create_payment_intent(
amount=float(amount),
description=description,
callback_url='https://yoursite.com/webhooks/payment-status'
)
if result['success']:
# Step 2: Redirect customer to checkout page
return redirect(result['checkout_url'])
else:
return JsonResponse({'error': result['error']}, status=400)
Checkout Endpoint URL Format
The checkout URL that you receive from the API follows this format:
Checkout URL
https://payset.okao.site/checkout/{reference}
{reference} is an unguessable PHV_xxxxx value — for example PHV_72UJVK2EN8.
Example Checkout URLs
Here are real examples of checkout URLs you'll receive:
https://payset.okao.site/checkout/PHV_72UJVK2EN8
https://payset.okao.site/checkout/PHV_DZ4LGW3QM0
How to Use the Checkout URL
When you receive the checkout_url from the response, simply redirect your customer to that URL:
// JavaScript/React
window.location.href = data.checkout_url;
// PHP
header('Location: ' . $data['checkout_url']);
exit;
// Python (Flask)
return redirect(result['checkout_url'])
// Python (Django)
return redirect(result['checkout_url'])
Important: Always use the checkout_url from the response. Do not construct the URL manually. The reference is generated server-side and is cryptographically secure. The format is always https://payset.okao.site/checkout/PHV_72UJVK2EN8 where PHV_72UJVK2EN8 is a unique reference (PHV_ + 10 characters).
Checkout URL Breakdown
| Component | Description | Example |
|---|---|---|
Base URL |
Your PayHive instance URL | https://payset.okao.site |
/checkout/ |
Checkout route prefix | /checkout/ |
Reference |
Unique payment intent reference (PHV_ + 10 characters) | PHV_72UJVK2EN8 |
Checkout flow
- Pay now — the customer clicks Pay on your site or app.
- Create the intent — your backend posts
amountanddescriptiontoPOST /api/v1/payments. - Checkout URL — the API returns
checkout_url, e.g.https://payset.okao.site/checkout/PHV_72UJVK2EN8. - Redirect — send the customer to that URL. Do not build it yourself.
- Hosted page — they see amount, currency, and card, bank, or wallet options.
- Pay — they confirm with their bank or wallet. You never handle card data.
- Webhook — if you set
callback_url, we POST the status to your server. - Done — the customer lands on your success page after completion.
Payment Intent Statuses
| Status | Description |
|---|---|
pending |
Payment intent created, waiting for customer to complete payment |
paid |
Payment successfully completed |
failed |
Payment failed (customer declined, insufficient funds, etc.) |
expired |
Payment intent expired (default: 24 hours after creation) |
Webhook Notifications
If you provided a callback_url when creating the payment intent, you will receive a POST request to that URL when the payment status changes.
Webhook Payload
POST https://yoursite.com/webhooks/payment-status
Content-Type: application/json
{
"reference": "PHV_72UJVK2EN8",
"status": "paid",
"transaction_id": "TXN-ABCDEFGHIJKL",
"amount": 50000.00,
"currency": "UGX",
"net_amount": 48500.00,
"timestamp": "2025-01-25T12:00:00Z"
}
Webhook Status Values
paid- Payment successfully completedfailed- Payment failed
Verifying Webhook Requests
Always verify that the webhook request is legitimate by:
- Checking the payment reference exists in your system
- Verifying the amount matches your records
- Updating your order/payment status only after verification
Checking Payment Status
After redirecting your customer to the checkout page, you can check the payment status in several ways:
Method 1: Status Endpoint (Recommended for Real-time Updates)
Poll the status endpoint to check if payment has been completed. The system automatically checks the payment provider directly if the webhook hasn't arrived yet.
Example Request
GET https://payset.okao.site/checkout/PHV_72UJVK2EN8/status
Headers:
Accept: application/json
Example Response
{
"success": true,
"data": {
"reference": "PHV_72UJVK2EN8",
"status": "paid",
"transaction_status": "completed",
"transaction_id": "TXN-ABCDEFGHIJKL",
"is_paid": true,
"is_completed": true
}
}
Automatic Payment Provider Check: The status endpoint automatically queries the payment provider's API directly if the payment is still pending. This ensures you get real-time status updates even if webhooks are delayed. The system uses the same reliable method as payment links.
JavaScript Polling Example
// Poll payment status every 2 seconds
function checkPaymentStatus(reference) {
return fetch(`https://payset.okao.site/checkout/${reference}/status`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
cache: 'no-cache'
})
.then(response => response.json())
.then(data => {
if (data.success && data.data) {
// Check if payment is completed
if (data.data.status === 'paid' ||
data.data.transaction_status === 'completed' ||
data.data.is_paid === true ||
data.data.is_completed === true) {
return { completed: true, status: data.data };
}
// Check if payment failed
if (data.data.status === 'failed' ||
data.data.transaction_status === 'failed') {
return { completed: true, failed: true, status: data.data };
}
// Still pending
return { completed: false, status: data.data };
}
return { completed: false, error: 'Invalid response' };
});
}
// Usage: Poll every 2 seconds until completed
let pollCount = 0;
const maxPolls = 60; // 2 minutes
const pollInterval = setInterval(async () => {
pollCount++;
if (pollCount > maxPolls) {
clearInterval(pollInterval);
console.log('Polling timeout');
return;
}
const result = await checkPaymentStatus('PHV_72UJVK2EN8');
if (result.completed) {
clearInterval(pollInterval);
if (result.failed) {
console.log('Payment failed');
} else {
console.log('Payment completed!', result.status);
// Update your UI or redirect
}
}
}, 2000); // Check every 2 seconds
Method 2: Webhook Notifications (Recommended for Server-side)
If you provided a callback_url, you'll receive a POST request when the payment status changes. This is the most reliable method for server-side applications.
Best Practice: Use webhooks for server-side status updates, and polling for client-side real-time updates. The status endpoint automatically checks the payment provider directly, so you'll get updates even if webhooks are delayed.
How Payment Status Detection Works
The system uses multiple methods to ensure reliable payment status detection:
- Webhook Processing: When the payment provider sends a webhook, the system updates the payment intent and transaction status immediately
- Direct Payment Provider API Check: If payment is still pending, the status endpoint queries the payment provider's API directly (same method as payment links)
- Automatic Status Sync: When checking status, the system automatically updates the payment intent if the payment provider shows it's completed
- Real-time Updates: The checkout page polls the status endpoint every 1-3 seconds and redirects to success when payment is detected
Reliability: The system uses the same proven status detection method as payment links, which includes direct payment provider API checks. This ensures payments are detected even if webhooks are delayed or missed.
Security Best Practices
- Never trust frontend data: All payment amounts, currency, and recipient information are stored server-side and fetched from the database
- Verify payment status: Always verify payment completion on your server before fulfilling orders
- Use HTTPS: Always use HTTPS for callback URLs
- Handle expiration: Payment intents expire after 24 hours (configurable). Handle expired payments gracefully
- Rate limiting: Checkout routes are rate-limited to prevent abuse
Testing
You can test the checkout page by visiting:
https://payset.okao.site/test-checkout
This will create a test payment intent and redirect you to the checkout page.
Troubleshooting
If you encounter issues with the checkout page, here are common problems and solutions:
"Checkout Endpoint Not Found" Error
If you receive the error message "PayHive checkout endpoint not found. Please contact support to verify your API configuration." when creating a payment intent:
- API Endpoint Issue: Verify you're using the correct endpoint:
POST /api/v1/payments - API Authentication: Ensure your
Authorization: Bearerheader is correct and your token is active - Route Configuration: This error typically occurs when the checkout URL cannot be generated. Check that the route
checkout.payment-intent.showexists - Base URL: Verify your application's
APP_URLis correctly configured in your.envfile
Solution: Check the response. The checkout_url should be in the format: https://payset.okao.site/checkout/PHV_72UJVK2EN8. If the URL is missing or incorrect, check your Laravel logs at storage/logs/laravel.log for route generation errors.
// Example of correct response:
{
"reference": "PHV_72UJVK2EN8",
"checkout_url": "https://payset.okao.site/checkout/PHV_72UJVK2EN8"
}
// If you receive an error instead, check:
// 1. API authentication headers
// 2. API endpoint URL (should be /api/v1/payments)
// 3. Server logs for detailed error messages
Checkout Page Shows "Service Temporarily Unavailable"
If customers see the error message "PayHive payment service is temporarily unavailable. Please try again in a few moments or contact support.", this usually indicates:
- Payment Provider Integration Not Configured: Ensure payment provider API tokens are configured in the admin settings (either live or sandbox tokens)
- Database Connection Issues: Check that the database is accessible and the
payment_intentstable exists - Service Error: Check the Laravel logs at
storage/logs/laravel.logfor detailed error messages
Solution: Verify your payment provider integration settings and check server logs. The checkout page will still load even if payment providers are disabled, but payment processing will fail.
Payment Intent Not Found (404 Error)
If you get a 404 error when accessing the checkout URL:
- Verify the payment intent reference is correct (format:
PHV_72UJVK2EN8or similar PHV_ format) - Check that the payment intent exists in the database
- Ensure the payment intent hasn't been deleted
Payment Intent Expired
If customers see an "expired" message:
- Payment intents expire after 24 hours by default (configurable)
- Create a new payment intent for the customer
- Handle expiration gracefully in your application by checking the
expires_atfield
No Payment Providers Available
If the checkout page loads but shows no mobile money options:
- Check that payment provider integration is configured and active
- Verify that MTN and/or Airtel providers are enabled in the payment provider integration settings
- Check the integration's
enabled_providersfield containsMTN_MOMO_UGAand/orAIRTEL_OAPI_UGA
Payment Processing Fails
If payment submission fails:
- Check that the phone number is in the correct format (Uganda: 9-10 digits, e.g., 0771234567)
- Verify the selected mobile money network matches the phone number (MTN vs Airtel)
- Check payment provider API logs for provider-specific errors
- Ensure the customer's mobile money account is active and can receive USSD prompts
Webhook Not Received
If you're not receiving webhook notifications:
- Verify your
callback_urlis publicly accessible (not localhost) - Check that your webhook endpoint accepts POST requests
- Verify your server can receive requests from PayHive's servers
- Check server logs for incoming webhook requests
- Ensure your webhook endpoint returns a 200 status code
Best Practice: Always verify payment completion via webhook or by checking the payment intent status server-side. Never rely solely on the customer being redirected to a success page.
Webhooks (Using Your Token)
Webhooks let you receive real-time notifications about payment and account events. When you create a payment or payment intent with your token, you can pass a callback_url; we will POST to that URL when the payment status changes.
Setting Up Webhooks
- Go to your dashboard and navigate to API Management → Webhooks
- Click Create Webhook
- Enter your webhook URL (must be HTTPS)
- Select the events you want to listen to
- Save your webhook secret (you'll need it to verify requests)
Webhook Events
| Event | Description |
|---|---|
payment.succeeded |
Triggered when a payment is successfully completed |
payment.failed |
Triggered when a payment fails |
payment.refunded |
Triggered when a payment is refunded |
payment.pending |
Triggered when a payment is pending |
Testing Webhooks Locally
A callback_url pointing at localhost will never receive a webhook. Our server sends the webhook from our infrastructure to the URL you provide — "localhost" in that request means our own server, not your laptop. There is no way for any external server to reach a URL like http://localhost:3000/webhook on your machine, no matter how the request is made. This isn't a bug in your integration; it's how networking works for any webhook-based API, not just ours.
Your payment still completes normally even when the webhook can't be delivered — it's only the notification to your app that fails silently. If you're testing locally, use one of these instead:
- Tunnel your local server (recommended): use a tool like ngrok to expose your local dev server on a public URL, then use that URL as your
callback_url.ngrok http 3000 # use the https://xxxx.ngrok-free.app URL it prints as your callback_url - Poll instead of waiting for the webhook: call
GET /api/v1/payments/{transaction_id}directly to check whether it succeeded, instead of relying only on the callback.
Error Handling
All errors follow a consistent format:
{
"success": false,
"error": {
"code": "invalid_request",
"message": "The request is missing required parameters",
"details": {
"field": "amount",
"reason": "Amount must be greater than 0"
}
}
}
Error Codes
| Error Code | HTTP Status | Description | Solution |
|---|---|---|---|
PROVIDER_NOT_CONFIGURED |
503 | Payment provider not configured | Contact support to configure payment provider |
PROVIDER_NOT_ENABLED |
400 | Requested provider (MTN/Airtel) is not enabled | Contact support to enable the provider or use a different provider |
FEE_EXCEEDS_AMOUNT |
400 | Total fees exceed transaction amount | Increase the transaction amount or adjust fee settings |
AUTH_FAILED |
401 | Invalid or missing token | Check that your Authorization: Bearer token is correct |
INVALID_REQUEST |
400 | Invalid request parameters | Check required fields: amount, currency, phone_number, provider |
NOT_FOUND |
404 | Transaction not found | Verify the transaction_id is correct |
Common Issues & Troubleshooting
Issue: "AUTH_FAILED" Error
Possible causes:
- Missing or incorrect token in headers
- Token is inactive or revoked
- Using sandbox keys in production or vice versa
Solution: Verify your token is correct and active in Dashboard → Tokens.
Issue: "PROVIDER_NOT_ENABLED" Error
Possible causes:
- The provider (MTN_MOMO_UGA or AIRTEL_OAPI_UGA) is not enabled for your account
- Typo in provider name
Solution: Use the correct provider name: MTN_MOMO_UGA or AIRTEL_OAPI_UGA. Contact support if the provider needs to be enabled.
Issue: Payment Stays in "pending" Status
Possible causes:
- Customer hasn't approved the payment on their phone
- Customer's phone is off or out of network coverage
- Insufficient mobile money balance
Solution: Wait for the customer to approve. Check payment status periodically. Payments typically complete within a few minutes.
Issue: Phone Number Format
Format: Phone numbers must be in international format without the + sign:
- Correct:
256700000000(Uganda MTN) - Incorrect:
+256700000000or0700000000
Rate Limiting
API requests are rate-limited to ensure fair usage:
- Sandbox: 100 requests per minute
- Live: 1000 requests per minute
Rate limit information is included in response headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1639123200
Direct Payments (Without Checkout Page)
You can use the same token (from Dashboard → Tokens at https://payset.okao.site/customer/tokens) to collect payments without redirecting customers to the hosted checkout page. This is useful when you want to keep users on your own website or app and collect the phone number and network yourself.
Two ways to accept payments with your token:
- Hosted checkout:
POST /api/v1/paymentswithamount,description,callback_url→ getcheckout_urland redirect the customer to our secure page. PAYHIIVE handles phone number entry and network selection. - Direct charge (no checkout):
POST /api/v1/chargeswithamount,currency,phone_number,provider→ we send the mobile money prompt directly to the customer's phone; they never leave your site. You own the entire UI.
Tokens generated from the customer dashboard work for both flows. Use the same Authorization: Bearer header.
Create a Direct Charge
Charge a mobile money number directly. You collect the customer's phone number and network (MTN or Airtel) on your own form; PAYHIIVE sends the mobile money prompt to their phone. No redirect. No checkout page.
POST /api/v1/payments/direct is kept as a backward-compatible alias — new integrations should use /charges.
Request Body
| Parameter | Type | Description | Required |
|---|---|---|---|
amount |
integer | Amount in UGX (e.g., 10000 = 10,000 UGX) | Required |
currency |
string | Currency code (UGX) | Required |
phone_number |
string | Customer mobile money number (e.g., 256700000000) | Required |
provider |
string | MTN_MOMO_UGA or AIRTEL_OAPI_UGA |
Required |
description |
string | Payment description shown to the customer | Optional |
callback_url |
string (URL) | Webhook URL — PAYHIIVE will POST the payment result here when the status changes | Optional |
metadata |
object | Additional key/value pairs stored with the transaction (e.g. {"order_id":"123"}) |
Optional |
Example Request
POST https://payset.okao.site/api/v1/charges
Headers:
Authorization: Bearer sk_your_token
Content-Type: application/json
Body:
{
"amount": 10000,
"currency": "UGX",
"phone_number": "256700000000",
"provider": "MTN_MOMO_UGA",
"description": "Order #123",
"callback_url": "https://yoursite.com/webhooks/payhiive"
}
Example Response (Success)
{
"success": true,
"data": {
"id": 123,
"transaction_id": "TXN-ABC123XYZ",
"deposit_id": "uuid-from-pawapay",
"amount": 10000,
"requested_amount": 10000,
"currency": "UGX",
"status": "pending",
"provider": "pawapay",
"net_amount": 10000,
"message": "Charge request accepted. The customer will receive a mobile money prompt.",
"created_at": "2025-12-10T12:00:00Z"
}
}
Status lifecycle: pending → completed or failed. The customer receives a mobile money USSD/push prompt on their phone to approve or decline. Status is updated via webhook or when you poll GET /api/v1/charges/{transaction_id}.
Sample Code (Copy & Paste)
React (JavaScript)
const API_BASE = 'https://payset.okao.site/api/v1';
const TOKEN = 'sk_your_token';
// Direct charge — customer stays on your page, receives mobile money prompt
async function createCharge(amount, phoneNumber, provider, description) {
const res = await fetch(`${API_BASE}/charges`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount,
currency: 'UGX',
phone_number: phoneNumber,
provider: provider || 'MTN_MOMO_UGA', // MTN_MOMO_UGA | AIRTEL_OAPI_UGA
description: description || '',
callback_url: 'https://yoursite.com/webhooks/payhiive', // optional
}),
});
const data = await res.json();
if (!data.success) throw new Error(data.message || 'Charge failed');
return data.data; // { transaction_id, status: "pending", ... }
}
// Poll status until completed or failed
async function getChargeStatus(transactionId) {
const res = await fetch(`${API_BASE}/charges/${transactionId}`, {
headers: { 'Authorization': `Bearer ${TOKEN}` },
});
const data = await res.json();
if (!data.success) throw new Error(data.message || 'Failed to get status');
return data.data; // { status, transaction_id, reference, ... }
}
// Usage
const charge = await createCharge(10000, '256700000000', 'MTN_MOMO_UGA', 'Order #123');
const txId = charge.transaction_id; // store this
// Poll every 5s until status is "completed" or "failed"
const result = await getChargeStatus(txId);
Python
import requests, time
API_BASE = 'https://payset.okao.site/api/v1'
TOKEN = 'sk_your_token'
HEADERS = {'Authorization': f'Bearer {TOKEN}', 'Content-Type': 'application/json'}
# Direct charge — customer stays on your page, receives mobile money prompt
def create_charge(amount, phone_number, provider='MTN_MOMO_UGA', description=''):
r = requests.post(f'{API_BASE}/charges', json={
'amount': amount,
'currency': 'UGX',
'phone_number': phone_number,
'provider': provider, # MTN_MOMO_UGA | AIRTEL_OAPI_UGA
'description': description,
'callback_url': 'https://yoursite.com/webhooks/payhiive', # optional
}, headers=HEADERS)
data = r.json()
if not data.get('success'):
raise Exception(data.get('message', 'Charge failed'))
return data['data'] # { transaction_id, status: "pending", ... }
# Poll status until completed or failed
def get_charge_status(transaction_id):
r = requests.get(f'{API_BASE}/charges/{transaction_id}', headers=HEADERS)
data = r.json()
if not data.get('success'):
raise Exception(data.get('message', 'Failed to get status'))
return data['data'] # { status, transaction_id, reference, ... }
# Usage
charge = create_charge(10000, '256700000000', 'MTN_MOMO_UGA', 'Order #123')
tx_id = charge['transaction_id'] # store this
# Poll every 5s until resolved
for _ in range(12):
time.sleep(5)
result = get_charge_status(tx_id)
if result['data']['status'] in ('completed', 'failed'):
break
PHP (Laravel)
use Illuminate\Support\Facades\Http;
$apiBase = 'https://payset.okao.site/api/v1';
$token = 'sk_your_token';
// Direct charge — customer stays on your page, receives mobile money prompt
function createCharge($amount, $phoneNumber, $provider = 'MTN_MOMO_UGA', $description = '') {
global $apiBase, $token;
$res = Http::withHeaders([
'Authorization' => 'Bearer ' . $token,
'Content-Type' => 'application/json',
])->post("{$apiBase}/charges", [
'amount' => $amount,
'currency' => 'UGX',
'phone_number' => $phoneNumber,
'provider' => $provider, // MTN_MOMO_UGA | AIRTEL_OAPI_UGA
'description' => $description,
'callback_url' => 'https://yoursite.com/webhooks/payhiive', // optional
]);
$data = $res->json();
if (!($data['success'] ?? false)) {
throw new \Exception($data['message'] ?? 'Charge failed');
}
return $data['data']; // { transaction_id, status: "pending", ... }
}
// Poll charge status
function getChargeStatus($transactionId) {
global $apiBase, $token;
$res = Http::withHeaders([
'Authorization' => 'Bearer ' . $token,
])->get("{$apiBase}/charges/{$transactionId}");
$data = $res->json();
if (!($data['success'] ?? false)) {
throw new \Exception($data['message'] ?? 'Failed to get status');
}
return $data['data']; // { status, transaction_id, reference, ... }
}
// Usage
$charge = createCharge(10000, '256700000000', 'MTN_MOMO_UGA', 'Order #123');
$txId = $charge['transaction_id']; // store this for polling / reconciliation
// Poll until completed or failed
for ($i = 0; $i < 12; $i++) {
sleep(5);
$result = getChargeStatus($txId);
if (in_array($result['status'], ['completed', 'failed'])) break;
}
The customer will receive a mobile money prompt on their phone. Track status via webhook or poll GET /api/v1/charges/{transaction_id}.
Same token everywhere. The token you create at https://payset.okao.site/customer/tokens works for both hosted checkout and direct charges. Use it with the same Authorization: Bearer header.
Integration Examples
Here are complete integration examples for different platforms and languages:
PHP (Laravel/Plain PHP)
<?php
// Configuration
$baseUrl = 'https://payset.okao.site/api/v1';
$token = 'sk_your_token';
// Create payment
function createPayment($amount, $phoneNumber, $provider, $description = '') {
global $baseUrl, $token;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $baseUrl . '/payments');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'amount' => $amount,
'currency' => 'UGX',
'phone_number' => $phoneNumber,
'provider' => $provider, // 'MTN_MOMO_UGA' or 'AIRTEL_OAPI_UGA'
'description' => $description
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 || $httpCode === 201) {
return json_decode($response, true);
}
return ['success' => false, 'error' => json_decode($response, true)];
}
// Usage
$result = createPayment(10000, '256700000000', 'MTN_MOMO_UGA', 'Order #123');
if ($result['success']) {
echo "Payment created: " . $result['data']['transaction_id'];
echo "\nNet amount: " . ($result['data']['net_amount'] ?? 'N/A') . " UGX";
echo "\nFee: " . ($result['data']['fee'] ?? 'N/A') . " UGX";
} else {
echo "Error: " . ($result['error']['message'] ?? $result['message'] ?? 'Unknown error');
}
?>
React (JavaScript/TypeScript)
// Install: npm install axios
import axios from 'axios';
const API_BASE_URL = 'https://payset.okao.site/api/v1';
const TOKEN = 'sk_your_token';
// Create payment function
async function createPayment(amount, phoneNumber, provider, description = '') {
try {
const response = await axios.post(
`${API_BASE_URL}/payments`,
{
amount: amount,
currency: 'UGX',
phone_number: phoneNumber,
provider: provider, // 'MTN_MOMO_UGA' or 'AIRTEL_OAPI_UGA'
description: description
},
{
headers: {
'Authorization': `Bearer ${TOKEN}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
} catch (error) {
console.error('Payment error:', error.response?.data || error.message);
throw error;
}
}
// React Component Example
function PaymentButton() {
const handlePayment = async () => {
try {
const result = await createPayment(
10000,
'256700000000',
'MTN_MOMO_UGA',
'Order #123'
);
if (result.success) {
console.log('Payment created:', result.data.transaction_id);
console.log('Net amount:', result.data.net_amount, 'UGX');
console.log('Fee:', result.data.fee, 'UGX');
alert('Payment initiated successfully!');
}
} catch (error) {
alert('Payment failed: ' + (error.response?.data?.error?.message || error.message));
}
};
return (
<button onClick={handlePayment}>
Pay 10,000 UGX
</button>
);
}
export default PaymentButton;
Python (Flask/Django)
# Install: pip install requests
import requests
import json
# Configuration
API_BASE_URL = 'https://payset.okao.site/api/v1'
TOKEN = 'sk_your_token'
def create_payment(amount, phone_number, provider, description=''):
"""
Create a mobile money payment
Args:
amount: Amount in UGX
phone_number: Customer phone number (e.g., '256700000000')
provider: 'MTN_MOMO_UGA' or 'AIRTEL_OAPI_UGA'
description: Payment description
Returns:
dict: API response
"""
url = f'{API_BASE_URL}/payments'
headers = {
'Authorization': f'Bearer {TOKEN}',
'Content-Type': 'application/json'
}
data = {
'amount': amount,
'currency': 'UGX',
'phone_number': phone_number,
'provider': provider,
'description': description
}
try:
response = requests.post(url, json=data, headers=headers)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f'Error: {e}')
if hasattr(e.response, 'json'):
return e.response.json()
return {'success': False, 'error': {'message': str(e)}}
# Usage example
if __name__ == '__main__':
result = create_payment(
amount=10000,
phone_number='256700000000',
provider='MTN_MOMO_UGA',
description='Order #123'
)
if result.get('success'):
print(f"Payment created: {result['data']['transaction_id']}")
print(f"Net amount: {result['data'].get('net_amount', 'N/A')} UGX")
print(f"Fee: {result['data'].get('fee', 'N/A')} UGX")
else:
print(f"Error: {result.get('error', {}).get('message', result.get('message', 'Unknown error'))}")
Security Note: Never expose your token in client-side code (React, browser JavaScript). Always make API calls from your backend server (PHP, Python, Node.js server) to keep your token secure.
Need help?
Questions about this API can go to the operator of this installation:
- Email: support@payset.okao.site
- Website: https://payset.okao.site
- Dashboard: Merchant login