Token & API Documentation

Integrate PAYHIIVE using your token for checkout, webhooks, and payments. Get your token from Dashboard → Tokens.

Getting Started

Welcome to the PAYHIIVE API documentation. This guide will help you integrate PAYHIIVE payment gateway into your applications.

Prerequisites

  • An active PAYHIIVE account
  • A token from your dashboard (Dashboard → Tokens)
  • Basic knowledge of REST APIs and HTTP requests

Step 1: Get Your Token

  1. Log in to your PAYHIIVE dashboard
  2. Go to Tokens (in the sidebar)
  3. Complete your profile (required to generate a token)
  4. Click "Generate Live Token" or use Sandbox for testing
  5. Choose your environment:
    • Sandbox: For testing (no real money)
    • Live: For production (requires KYC approval)
  6. Copy your token (it is shown once; store it securely)

Important: Your token is only shown once when generated. Copy and store it securely. If you lose it, generate a new token from Dashboard → Tokens.

Note: Use a Sandbox token for testing and a Live token for production. Never expose your token in client-side code (browser, mobile apps). Always call the API from your backend server.

Step 2: Test Your Connection

Before integrating, test your token using the test feature on the Tokens page or by making a test API call.

SDK & Plugins

Integrate PAYHIIVE 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 PAYHIIVE API.

npm install payhiive-node

Python Library

Official Python library for the PAYHIIVE API.

pip install payhiive-python

PHP Library

Official PHP library for the PAYHIIVE API.

composer require payhiive/payhiive-php

Available Plugins

WordPress Plugin

Accept mobile money payments on your WordPress site with our official plugin.

Installation Guides

WordPress Plugin Installation

  1. Download the plugin zip file from above
  2. Go to WordPress admin panel → Plugins → Add New → Upload Plugin
  3. Upload the downloaded zip file and click "Install Now"
  4. After installation, click "Activate Plugin"
  5. Go to PAYHIIVE in the WordPress menu and enter your token (from Dashboard → Tokens)
  6. Configure your payment settings and start accepting payments!

Authentication (Using Your Token)

All API requests require your token. Get it from Dashboard → Tokens. Your token starts with sk_ — it is the only key you need.

Headers

Header Description Required
Authorization Bearer sk_your_token — your token from Dashboard → Tokens. Required
Content-Type Should be application/json Required

Example Request

Use your token in the Authorization header when making any API call:

curl -X POST https://payhiive.com/api/v1/payments \ -H "Authorization: Bearer sk_your_token" \ -H "Content-Type: application/json" \ -d '{ "amount": 10000, "currency": "UGX", "phone_number": "256700000000", "provider": "MTN_MOMO_UGA", "description": "Payment for order #123" }'

Security: Never expose your token in client-side code. Keep it only on your server.

Fees & Pricing

PAYHIIVE charges platform fees on transactions. Fees are added on top of the amount you want to collect, so you receive the exact amount you requested.

API Transaction Fees

For transactions made through the API:

Fee Percentage
1.50%
Fixed Fee
0 UGX

Payment Link Transaction Fees

For transactions made through payment links:

Fee Percentage
2.98%
Fixed Fee
0 UGX

API Fee Calculation Example (Fees On Top)

If you want to collect 10,000 UGX via API:

  • Amount You Want to Collect: 10,000 UGX
  • Platform Fee (1.50% + 0): 150 UGX
  • Total Amount Customer Pays: 10,150 UGX
  • Amount Credited to Your Account: 10,000 UGX (exact amount you requested)

Payment Link Fee Calculation Example (Fees On Top)

If you want to collect 10,000 UGX via Payment Link:

  • Amount You Want to Collect: 10,000 UGX
  • Platform Fee (2.98% + 0): 298 UGX
  • Total Amount Customer Pays: 10,298 UGX
  • Amount Credited to Your Account: 10,000 UGX (exact amount you requested)

Payout/Withdrawal Fees

For withdrawals/payouts made through the API:

Fee Percentage
0.00%
Fixed Fee
0 UGX

Payout Fee Calculation Example

If you withdraw 10,000 UGX:

  • Withdrawal Amount: 10,000 UGX
  • Payout Fee (0.00% + 0): 0 UGX
  • Amount Sent to Recipient: 10,000 UGX

Note: Fees are added on top of the transaction amount. If you want to collect 10,000 UGX, the customer will pay 10,000 UGX plus the platform fee, and you will receive the full 10,000 UGX in your account.

Base URL

The base URL for all API requests:

https://payhiive.com/api/v1

Important: Always use HTTPS in production. Replace 127.0.0.1:8001 with your actual domain when going live.

API Endpoints

Payments

Create Payment

POST /payments

Create a new payment transaction.

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
metadata object Additional metadata (key-value pairs) Optional

Example Request

POST https://payhiive.com/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", "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

GET /payments/{transaction_id}

Retrieve the current status and details of a specific payment transaction.

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:

  1. From Payment Response: When you create a payment using POST /payments, the reference is included in the response.
  2. From Status Check: When you retrieve payment status using GET /payments/{transaction_id}, the reference field is included in the response data.
  3. From Dashboard: You can also view the transaction reference in your merchant dashboard at https://payhiive.com/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
  1. Log in to your merchant dashboard at https://payhiive.com/customer/login
  2. Navigate to Dashboard → Tokens
  3. Generate a new token (Dashboard → Tokens)
  4. 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://payhiive.com/api/v1
Step 2: Create a Payment Request

When creating a payment, make a POST request to the payments endpoint:

POST https://payhiive.com/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://payhiive.com/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://payhiive.com/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://payhiive.com/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://payhiive.com/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:

  1. Log in to your dashboard at https://payhiive.com/customer/login
  2. Navigate to Dashboard → Recent Transactions
  3. Find your transaction and check the Reference column
  4. The reference will be displayed in UUID format (e.g., b6a677d8-8a3d-4d2d-9de5-d439510d7c62)

Best Practices:

  • Always store both transaction_id and reference in your database
  • Use the reference for reconciliation with payment provider records
  • Use the transaction_id for 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

GET /payments

Retrieve a list of all your payments. Supports pagination and filtering.

Payouts

Payouts let you send money from your PAYHIIVE 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
  1. Your account accumulates a balance from payments your customers make.
  2. You call POST /api/v1/payouts with your secret token.
  3. PAYHIIVE deducts the amount (plus any fee) from your balance and forwards the net to the recipient's mobile number.
  4. In sandbox mode, the payout completes instantly with no real money moved.
  5. In live mode, the payout is submitted to the payment provider and returns processing; poll GET /payouts/{payout_id} for the final status.

Sandbox vs Live

FeatureSandboxLive
Token prefixsk_ (sandbox key)sk_ (live key)
Real money movedNoYes
KYC requiredNoYes — must be approved
Real balance requiredNo (simulated)Yes
Payout status on successcompleted immediatelyprocessing → poll for final
Minimum amount enforcedNoYes
SMS sent to recipientNoYes
Before going live, make sure:
  • 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

POST /api/v1/payouts

Sends money from your PAYHIIVE balance to a recipient's mobile money number. Supports two payload formats — flat fields or a nested recipient object.

Request Headers

HeaderValueRequired
AuthorizationBearer sk_your_secret_tokenRequired
Content-Typeapplication/jsonRequired
Acceptapplication/jsonRecommended

Request Body

ParameterTypeRequiredDescription
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://payhiive.com/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://payhiive.com/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

FieldTypeDescription
idintegerInternal payout record ID.
payout_idstring (UUID)The UUID used to track this payout. Use this in GET /payouts/{payout_id}.
amountdecimalThe amount you requested to send.
currencystringCurrency code.
feedecimalFee deducted from your balance on top of the amount.
net_amountdecimalAmount the recipient actually receives (amount minus fee).
statusstringpendingprocessingcompleted or failed.
sandbox_modebooleanPresent and true only on sandbox responses.
created_atISO 8601Timestamp of when the payout was created.

Payout Status Lifecycle

StatusMeaningAction required
pendingCreated but not yet sent to provider.Poll for update.
processingSubmitted to payment provider, awaiting confirmation.Poll GET /payouts/{payout_id}.
completedRecipient received the funds.None — done.
failedProvider rejected or could not complete payout. Balance is restored.Check rejection_reason and retry if appropriate.

Get Payout Status

GET /api/v1/payouts/{payout_id}

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://payhiive.com/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

GET /api/v1/payouts

Returns a paginated list of your payouts, newest first.

Query Parameters

ParameterTypeDescription
statusstringFilter by status: pending, processing, completed, failed.
from_datedateFilter payouts from this date (inclusive). Format: YYYY-MM-DD.
to_datedateFilter payouts up to this date (inclusive). Format: YYYY-MM-DD.
per_pageintegerResults per page. Default: 20. Max: 100.

Example

GET https://payhiive.com/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

HTTPerror_codeMeaning & fix
401UNAUTHORIZEDMissing or invalid token. Check your Authorization header.
403KYC_NOT_APPROVEDLive payouts require an approved KYC. Submit KYC from your dashboard.
400INSUFFICIENT_BALANCEYour balance is below the requested amount. Response includes available_balance.
400AMOUNT_BELOW_MINIMUMNet amount after fees is below platform minimum. Retry with minimum_request_amount from the response.
400PROVIDER_NOT_ENABLEDThe provider you specified is not active. Contact support.
400PAYOUTS_NOT_ALLOWED / PAYOUT_REJECTEDPayment provider rejected the payout. Your balance was not deducted. Check provider configuration.
503PROVIDER_NOT_CONFIGUREDPlatform payment provider is not set up. Contact the platform admin.
422Validation 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://payhiive.com/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://payhiive.com/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://payhiive.com/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://payhiive.com'; $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://payhiive.com' 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
  • Always generate and store your own payout_id (UUID v4) before calling the API. If a network timeout occurs you can still retrieve the payout by that ID.
  • Never expose your secret token (sk_...) in client-side JavaScript or mobile app code. Make payout API calls from your backend only.
  • Use sandbox mode during development — no real money is moved and payouts complete instantly.
  • For live mode, implement status polling on GET /payouts/{payout_id} until the status is completed or failed.
  • Handle the INSUFFICIENT_BALANCE error gracefully — show the available_balance field from the response to help users understand the shortfall.
  • If you receive AMOUNT_BELOW_MINIMUM, use the minimum_request_amount field from the response as the minimum allowed value for a retry.

Refunds

Create Refund

POST /refunds

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)

PAYHIIVE 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

POST /payments

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://payhiive.com/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_PO6JMNWHCD", "checkout_url": "https://payhiive.com/checkout/PHV_PO6JMNWHCD" }

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://payhiive.com/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://payhiive.com/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

GET /checkout/{reference}/status

Check the status of a payment intent using the reference returned when creating the payment.

Example Request

GET https://payhiive.com/checkout/PHV_PO6JMNWHCD/status Headers: Accept: application/json

Example Response

{ "success": true, "data": { "reference": "PHV_PO6JMNWHCD", "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 PAYHIIVE 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.

Payment Intent Checkout (Hosted Checkout)

PAYHIIVE provides a secure hosted checkout page for payment intents. This is the recommended way to accept payments as it ensures merchants never control payment amounts, currency, or recipient information on the frontend. All payment data is stored server-side and fetched from the database using a secure, unguessable reference.

⚡ Quick Start - Copy & Paste Ready

Copy these code snippets and replace with your token. That's it!

Default Checkout URL Format (Copy This)

https://payhiive.com/checkout/PHV_PO6JMNWHCD

This is an example. Replace PHV_PO6JMNWHCD with the actual reference from the response

JavaScript/React - Copy & Paste

// Step 1: Replace with your token from Dashboard → Tokens
const API_BASE_URL = 'https://payhiive.com/api/v1';
const TOKEN = 'sk_your_token';  // Your token from Dashboard → Tokens

// Step 2: Function to create payment and redirect to checkout
async function redirectToPayHiveCheckout(amount, description) {
  try {
    // Create payment intent via API
    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., 50000.00 for 50,000 UGX
        description: description,  // e.g., "Order #12345"
        callback_url: 'https://yoursite.com/webhooks/payment'  // Optional: for webhooks
      })
    });

    const data = await response.json();

    // Step 3: Redirect customer to checkout page
    if (data.checkout_url) {
      window.location.href = data.checkout_url;  // ← This redirects to PayHive checkout
      return { success: true, reference: data.reference };
    } else {
      throw new Error(data.message || 'Failed to create payment');
    }
  } catch (error) {
    console.error('Payment error:', error);
    alert('Payment failed: ' + error.message);
    return { success: false, error: error.message };
  }
}

// Step 4: Use it in your "Pay Now" button
document.getElementById('pay-button').addEventListener('click', async () => {
  await redirectToPayHiveCheckout(50000.00, 'Payment for Order #12345');
  // Customer will be automatically redirected to checkout page
});

// Notes:
// - Replace API_BASE_URL with your PayHive instance URL
// - Get your token from: Dashboard → Tokens
// - The checkout_url from the response is the redirect destination
// - Amount is in smallest currency unit (50000.00 = 50,000 UGX)
// - Always call this from your backend in production (keep token secure)

PHP - Copy & Paste

<?php
// Step 1: Replace with your token from Dashboard → Tokens
$API_BASE_URL = 'https://payhiive.com/api/v1';
$TOKEN = 'sk_your_token';  // Your token from Dashboard → Tokens

// Step 2: Function to create payment and redirect to checkout
function redirectToPayHiveCheckout($amount, $description) {
    global $API_BASE_URL, $TOKEN;
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $API_BASE_URL . '/payments');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Authorization: Bearer ' . $TOKEN,
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'amount' => $amount,           // e.g., 50000.00 for 50,000 UGX
        'description' => $description, // e.g., "Order #12345"
        'callback_url' => 'https://yoursite.com/webhooks/payment'  // Optional
    ]));
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    $data = json_decode($response, true);
    
    // Step 3: Redirect customer to checkout page
    if ($httpCode === 201 && isset($data['checkout_url'])) {
        header('Location: ' . $data['checkout_url']);  // ← This redirects to PayHive checkout
        exit;
    } else {
        die('Payment failed: ' . ($data['message'] ?? 'Unknown error'));
    }
}

// Step 4: Use it when customer clicks "Pay Now"
if (isset($_POST['pay_now'])) {
    redirectToPayHiveCheckout(50000.00, 'Payment for Order #12345');
    // Customer will be automatically redirected to checkout page
}

// Notes:
// - Replace $API_BASE_URL with your PayHive instance URL
// - Get your token from: Dashboard → Tokens
// - The checkout_url from the response is the redirect destination
// - Amount is in smallest currency unit (50000.00 = 50,000 UGX)
// - Always keep token on server, never expose in frontend

Python (Flask/Django) - Copy & Paste

import requests
from flask import redirect  # or: from django.shortcuts import redirect

# Step 1: Replace with your token from Dashboard → Tokens
API_BASE_URL = 'https://payhiive.com/api/v1'
TOKEN = 'sk_your_token'  # Your token from Dashboard → Tokens

# Step 2: Function to create payment and redirect to checkout
def redirect_to_payhive_checkout(amount, description):
    url = f'{API_BASE_URL}/payments'
    headers = {
        'Authorization': f'Bearer {TOKEN}',
        'Content-Type': 'application/json'
    }
    data = {
        'amount': amount,           # e.g., 50000.00 for 50,000 UGX
        'description': description,  # e.g., "Order #12345"
        'callback_url': 'https://yoursite.com/webhooks/payment'  # Optional
    }
    
    response = requests.post(url, json=data, headers=headers)
    result = response.json()
    
    # Step 3: Redirect customer to checkout page
    if response.status_code == 201 and 'checkout_url' in result:
        return redirect(result['checkout_url'])  # ← This redirects to PayHive checkout
    else:
        return {'error': result.get('message', 'Payment failed')}

# Step 4: Use it in your route/view
@app.route('/pay', methods=['POST'])  # Flask example
def pay():
    return redirect_to_payhive_checkout(50000.00, 'Payment for Order #12345')
    # Customer will be automatically redirected to checkout page

# Notes:
# - Replace API_BASE_URL with your PayHive instance URL
# - Get your token from: Dashboard → Tokens
# - The checkout_url from the response is the redirect destination
# - Amount is in smallest currency unit (50000.00 = 50,000 UGX)
# - Keep your token on server, never expose in frontend
# - Install requests: pip install requests

That's it! Just copy the code, replace your token, and redirect to checkout_url. The checkout page handles everything else.

Security Features:

  • Unguessable payment references (PHV_xxxxx format)
  • Amount, currency, and recipient are NEVER read from request
  • All data fetched from database using reference
  • Automatic expiration (configurable, default: 24 hours)
  • Rate limiting on checkout routes
  • No token exposed in frontend

Quick Start Guide: Step-by-Step Instructions

Follow these simple steps to integrate the hosted checkout page into your application:

Step 1: Create a Payment Intent

Make a POST request to /api/v1/payments with the payment amount and description.

POST https://payhiive.com/api/v1/payments Headers: Authorization: Bearer sk_your_token Content-Type: application/json Body: { "amount": 50000.00, "description": "Payment for Order #12345" }

Step 2: Get the Checkout URL

The API will return a response with a checkout_url:

{ "reference": "PHV_PO6JMNWHCD", "checkout_url": "https://payhiive.com/checkout/PHV_PO6JMNWHCD" }

Step 3: Redirect Customer to Checkout

Redirect your customer to the checkout_url from the response. The customer will see a secure checkout page where they can complete the payment.

Important: Always redirect to the checkout_url provided in the response. Never construct the URL manually or trust frontend data.

Step 4: Handle Payment Completion

After the customer completes payment, you can:

  • Use Webhooks: If you provided a callback_url, you'll receive a POST request when payment status changes
  • Poll for Status: Check payment status by querying the payment intent using 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

POST /api/v1/payments

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://payhiive.com/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_PO6JMNWHCD", "checkout_url": "https://payhiive.com/checkout/PHV_PO6JMNWHCD" }

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://payhiive.com/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://payhiive.com/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://payhiive.com/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 Structure

https://payhiive.com/checkout/{reference}

Where {reference} is the unguessable payment reference in the format PHV_xxxxx (e.g., PHV_PO6JMNWHCD).

Example Checkout URLs

Here are real examples of checkout URLs you'll receive:

// Example 1: Your actual checkout URL (from your system) https://payhiive.com/checkout/PHV_PO6JMNWHCD // Example 2: Another example format https://payhiive.com/checkout/PHV_DZ4LGW3QM0 // Example 3: Production example (when app_url is set in database) https://payhiive.com/checkout/PHV_PO6JMNWHCD

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://payhiive.com/checkout/PHV_PO6JMNWHCD where PHV_PO6JMNWHCD is a unique reference (PHV_ + 10 characters).

Checkout URL Breakdown

Component Description Example
Base URL Your PayHive instance URL https://payhiive.com
/checkout/ Checkout route prefix /checkout/
Reference Unique payment intent reference (PHV_ + 10 characters) PHV_PO6JMNWHCD

Complete Flow Diagram

  1. Customer clicks "Pay Now" on your website/app
  2. Your backend calls POST /api/v1/payments with amount and description
  3. API returns checkout_url (e.g., https://payhiive.com/checkout/PHV_PO6JMNWHCD)
  4. Redirect customer to the checkout_url
  5. Customer sees secure checkout page with payment amount and mobile money options
  6. Customer enters phone number and selects network (MTN/Airtel)
  7. Payment processed via mobile money provider - customer approves on their phone
  8. Webhook sent to your callback_url (if provided) with payment status
  9. Customer redirected to success page after payment completes

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_PO6JMNWHCD", "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 completed
  • failed - Payment failed

Verifying Webhook Requests

Always verify that the webhook request is legitimate by:

  1. Checking the payment reference exists in your system
  2. Verifying the amount matches your records
  3. 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.

GET /checkout/{reference}/status
Example Request
GET https://payhiive.com/checkout/PHV_PO6JMNWHCD/status Headers: Accept: application/json
Example Response
{ "success": true, "data": { "reference": "PHV_PO6JMNWHCD", "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://payhiive.com/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_PO6JMNWHCD'); 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:

  1. Webhook Processing: When the payment provider sends a webhook, the system updates the payment intent and transaction status immediately
  2. 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)
  3. Automatic Status Sync: When checking status, the system automatically updates the payment intent if the payment provider shows it's completed
  4. 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://payhiive.com/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: Bearer header 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.show exists
  • Base URL: Verify your application's APP_URL is correctly configured in your .env file

Solution: Check the response. The checkout_url should be in the format: https://payhiive.com/checkout/PHV_PO6JMNWHCD. 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_PO6JMNWHCD", "checkout_url": "https://payhiive.com/checkout/PHV_PO6JMNWHCD" } // 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_intents table exists
  • Service Error: Check the Laravel logs at storage/logs/laravel.log for 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_PO6JMNWHCD or 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_at field

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_providers field contains MTN_MOMO_UGA and/or AIRTEL_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_url is 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

  1. Go to your dashboard and navigate to API Management → Webhooks
  2. Click Create Webhook
  3. Enter your webhook URL (must be HTTPS)
  4. Select the events you want to listen to
  5. 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

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: +256700000000 or 0700000000

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://payhiive.com/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/payments with amount, description, callback_url → get checkout_url and redirect the customer to our secure page. PAYHIIVE handles phone number entry and network selection.
  • Direct charge (no checkout): POST /api/v1/charges with amount, 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

POST /charges

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://payhiive.com/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: pendingcompleted 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://payhiive.com/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://payhiive.com/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://payhiive.com/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://payhiive.com/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://payhiive.com/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://payhiive.com/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://payhiive.com/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?

If you have any questions or need assistance with the API, please contact our support team: