Getting Started
The Hanypay Merchant API lets you accept AKL payments from customers, send payouts, issue refunds, and query transaction history. All communication is over HTTPS and authenticated via API keys.
Quick start
- Create a business in the Hanypay Dashboard
- Go to Business → API Keys and create an API key
- Copy the key — it is shown only once
- Pass the key in the
Hanypay-Api-Keyheader on every request
Authentication
Include your API key in every request using the Hanypay-Api-Key header.
curl https://payment-ms.hanypay.co/api/v1/merchant/charges/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{"amount": "100.00"}'All responses follow a consistent shape:
{
"success": true,
"message": "...",
"data": { ... }
}{
"success": false,
"error": "error_code",
"message": "Human-readable error message"
}Users
Create User
Create a new Hanypay user on behalf of your platform. The user gets a wallet automatically and can be used for subsequent payment collections and payouts. If a user with the same email already exists, their existing details are returned.
https://account-ms.hanypay.co/apikeys| Field | Type | Required | Description |
|---|---|---|---|
| string | Required | User's email address | |
| phone | string | Required | User's phone number (8-15 digits) |
| first_name | string | Required | User's first name |
| last_name | string | Required | User's last name |
| country | string | Required | Country code (e.g. "GH"), country name (e.g. "Ghana"), or country UUID |
| password | string | Optional | User password (auto-generated if omitted) |
curl -X POST https://account-ms.hanypay.co/apikeys/merchant/signup/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{
"email": "customer@example.com",
"phone": "0241234567",
"first_name": "John",
"last_name": "Doe",
"country": "GH"
}'{
"status": true,
"message": "User created successfully.",
"data": {
"user_id": "a1b2c3d4-...",
"email": "customer@example.com",
"wallet_number": "1234567890123456",
"is_new": true
}
}{
"status": true,
"message": "User already exists.",
"data": {
"user_id": "a1b2c3d4-...",
"email": "customer@example.com",
"wallet_number": "1234567890123456",
"is_new": false
}
}Error codes: invalid_api_key, invalid_country, phone_taken, creation_failed
Consent
Request Consent
Request a user's consent to view their wallet balance. The user receives an OTP via email which must be verified to grant access.
https://account-ms.hanypay.co/apikeys. You can only request consent for users your business created via the Create User endpoint.| Field | Type | Required | Description |
|---|---|---|---|
| string | Optional | User's email address (provide email or user_id) | |
| user_id | uuid | Optional | User ID from signup response (provide email or user_id) |
curl -X POST https://account-ms.hanypay.co/apikeys/merchant/consent/request/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{ "email": "customer@example.com" }'{
"status": true,
"message": "Consent code sent to user."
}Verify Consent
Submit the consent OTP code to grant your business access to the user's wallet balance. Consent is permanent until explicitly revoked.
| Field | Type | Required | Description |
|---|---|---|---|
| string | Optional | User's email address (provide email or user_id) | |
| user_id | uuid | Optional | User ID (provide email or user_id) |
| code | string | Required | 8-digit OTP code from the user |
curl -X POST https://account-ms.hanypay.co/apikeys/merchant/consent/verify/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{
"email": "customer@example.com",
"code": "12345678"
}'{
"status": true,
"message": "Consent granted.",
"data": {
"user_id": "a1b2c3d4-...",
"consent_id": "e5f6g7h8-..."
}
}Error codes: invalid_user, invalid_code, code_expired
Revoke Consent
Revoke a previously granted consent. After revocation, balance queries for this user will be denied until consent is granted again.
| Field | Type | Required | Description |
|---|---|---|---|
| string | Optional | User's email address (provide email or user_id) | |
| user_id | uuid | Optional | User ID (provide email or user_id) |
curl -X POST https://account-ms.hanypay.co/apikeys/merchant/consent/revoke/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{ "email": "customer@example.com" }'{
"status": true,
"message": "Consent revoked."
}Wallets
Get Balance
Retrieve the wallet balances of a user your business created. Requires active consent from the user (see Consent endpoints above).
https://account-ms.hanypay.co/apikeyscurl https://account-ms.hanypay.co/apikeys/merchant/user/USER_ID_HERE/balance/ \ -H "Hanypay-Api-Key: YOUR_API_KEY"
{
"status": true,
"message": "Balance retrieved.",
"data": {
"user_id": "a1b2c3d4-...",
"wallets": [
{
"wallet_number": "1234567890123456",
"currency_code": "AKL",
"balance": "500.000000"
}
]
}
}Error codes: invalid_user, no_consent
Collections
Initiate Payment
Start a payment collection. The customer receives a 6-digit authorization code via email which must be verified to complete the payment.
| Field | Type | Required | Description |
|---|---|---|---|
| wallet_number | string(16) | Required | Customer's wallet number |
| amount | decimal | Required | Amount in AKL (min 0.000001) |
| currency_code | string(3) | Optional | Currency code (only "AKL", default "AKL") |
| txn_reference | string(256) | Optional | Your unique reference for idempotency |
| metadata | object | Optional | Arbitrary key-value data returned in webhooks |
| callback_url | url | Optional | URL to receive webhook notifications |
| charge_bearer | string | Optional | "customer" (default) or "merchant". Determines who pays the transaction fee. |
charge_bearer to "merchant" to absorb the transaction fee yourself. The customer will only be charged the payment amount. Default is "customer", where the fee is added on top of the amount.curl -X POST https://payment-ms.hanypay.co/api/v1/merchant/payments/initiate/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{
"wallet_number": "1234567890123456",
"amount": "100.00",
"txn_reference": "ORDER-001",
"charge_bearer": "merchant"
}'{
"success": true,
"message": "Authorization code has been sent to customer.",
"data": {
"transaction_id": "d4f8a2b1-...",
"txn_reference": "ORDER-001",
"verification_required": true
}
}txn_reference that already exists, the existing transaction is returned instead of creating a duplicate. Use this for safe retries.Verify Payment
Submit the customer's authorization code to complete the payment. The result is synchronous — you get success or failure immediately.
| Field | Type | Required | Description |
|---|---|---|---|
| code | string | Required | 6-character authorization code from customer |
| txn_reference | string | Required | Transaction reference from initiate response |
curl -X POST https://payment-ms.hanypay.co/api/v1/merchant/payments/verify/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{
"code": "ABC123",
"txn_reference": "ORDER-001"
}'{
"success": true,
"message": "Payment completed successfully.",
"data": {
"transaction_id": "d4f8a2b1-...",
"status": "Completed",
"amount": "100.000000"
}
}Error codes: invalid_code, code_expired, invalid_reference, processing_failed
Payout / Disbursement
Payout
Send AKL from your business wallet to a recipient wallet.
| Field | Type | Required | Description |
|---|---|---|---|
| wallet_number | string(16) | Required | Recipient wallet number |
| amount | decimal | Required | Amount in AKL |
| description | string(255) | Optional | Payout description |
curl -X POST https://payment-ms.hanypay.co/api/v1/merchant/payouts/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{
"wallet_number": "5555666677778888",
"amount": "50.00",
"description": "Salary payout"
}'{
"success": true,
"message": "Payout completed successfully.",
"data": {
"transaction_id": "a1b2c3d4-...",
"reference": "REF-001",
"status": "Completed"
}
}Error codes: invalid_wallet, limit_exceeded, same_wallet, payout_failed
Transactions
List Transactions
Retrieve a paginated list of your business transactions.
| Field | Type | Required | Description |
|---|---|---|---|
| status | string | Optional | Filter by status (Pending, Completed, Failed, etc.) |
| transaction_type | string | Optional | Filter by type (Purchase, Transfer, Refund) |
| start_date | date | Optional | Filter from date (YYYY-MM-DD) |
| end_date | date | Optional | Filter to date (YYYY-MM-DD) |
| query | string | Optional | Search by reference or wallet number |
| page | integer | Optional | Page number (default 1) |
| page_size | integer | Optional | Results per page (default 15) |
curl "https://payment-ms.hanypay.co/api/v1/merchant/transactions/?status=Completed&page=1" \ -H "Hanypay-Api-Key: YOUR_API_KEY"
{
"count": 50,
"next": "...?page=2",
"previous": null,
"total_pages": 4,
"current_page": 1,
"results": [
{
"reference": "abc123",
"txn_reference": "ORDER-001",
"amount": "100.000000",
"status": "Completed",
...
}
]
}Get Transaction
Retrieve a single transaction by its internal reference or your txn_reference.
curl https://payment-ms.hanypay.co/api/v1/merchant/transactions/ORDER-001/ \ -H "Hanypay-Api-Key: YOUR_API_KEY"
{
"success": true,
"message": "Transaction retrieved.",
"data": {
"id": 123,
"reference": "abc123",
"txn_reference": "ORDER-001",
"amount": "100.000000",
"charges": "1.500000",
"currency_code": "AKL",
"transaction_type": "Purchase",
"status": "Completed",
"channel": "Wallet Transfer",
"description": "",
"metadata": {},
"created_at": "2026-01-15T10:30:00Z"
}
}Refund
Refund a completed purchase transaction. Supports full and partial refunds. Cumulative refunds are tracked — the total cannot exceed the original amount.
| Field | Type | Required | Description |
|---|---|---|---|
| transaction_reference | string | Required | Reference of the original transaction |
| amount | decimal | Optional | Partial refund amount (defaults to full amount) |
| description | string(255) | Optional | Reason for refund |
curl -X POST https://payment-ms.hanypay.co/api/v1/merchant/refunds/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{
"transaction_reference": "ORDER-001",
"amount": "25.00",
"description": "Customer requested partial refund"
}'{
"success": true,
"message": "Refund completed successfully.",
"data": {
"refund_reference": "REF-REFUND-001",
"amount": "25.0",
"status": "Completed"
}
}Utilities
Calculate Charges
Preview the transaction fee for a given amount before initiating a payment.
| Field | Type | Required | Description |
|---|---|---|---|
| amount | decimal | Required | Transaction amount |
curl -X POST https://payment-ms.hanypay.co/api/v1/merchant/charges/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{"amount": "100.00"}'{
"success": true,
"message": "Charges calculated.",
"data": {
"amount": "100.000000",
"charges": "1.500000",
"total": "101.500000"
}
}Exchange Rates
Convert between AKL and other currencies. One of the currencies must be AKL.
| Field | Type | Required | Description |
|---|---|---|---|
| base_currency_code | string(3) | Required | Source currency (e.g. "AKL") |
| target_currency_code | string(3) | Required | Target currency (e.g. "USD") |
| amount | decimal | Required | Amount to convert |
curl -X POST https://payment-ms.hanypay.co/api/v1/merchant/exchange-rates/ \
-H "Content-Type: application/json" \
-H "Hanypay-Api-Key: YOUR_API_KEY" \
-d '{
"base_currency_code": "AKL",
"target_currency_code": "USD",
"amount": "100.00"
}'{
"success": true,
"message": "Exchange rate retrieved.",
"data": {
"base_currency": "AKL",
"target_currency": "USD",
"rate": 0.052,
"original_amount": 100.0,
"converted_amount": 5.2
}
}Guides
Payment Flow
The collection flow involves two API calls with a customer action in between:
Webhooks
Webhook URLs are registered in the Dashboard under Business → API Keys → Webhook URLs. When a payment completes or fails, we send a POST request to each active URL.
Payload format
{
"event": "payment.completed",
"data": {
"reference": "abc123",
"txn_reference": "ORDER-001",
"amount": "100.000000",
"charges": "1.500000",
"currency_code": "AKL",
"status": "Completed",
"transaction_type": "Purchase",
"created_at": "2026-01-15T10:30:00Z"
}
}Events
| Event | Description |
|---|---|
| payment.completed | Customer payment succeeded |
| payment.failed | Customer payment failed |
| payment.updated | Payment status changed |
| payout.completed | Disbursement succeeded |
| payout.failed | Disbursement failed |
Signature verification
Each webhook includes an X-Hanypay-Signature header containing an HMAC-SHA256 hex digest of the JSON body signed with your webhook secret.
import hmac, hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)Error Reference
| Code | Description |
|---|---|
| invalid_api_key | Missing or invalid API key |
| invalid_wallet | Wallet number not found |
| insufficient_balance | Not enough AKL balance |
| invalid_code | Authorization code not found |
| code_expired | Authorization code has expired |
| invalid_reference | Transaction reference not found |
| invalid_amount | Amount exceeds allowed limit |
| processing_failed | Payment processing failed |
| limit_exceeded | Transaction limit exceeded |
| same_wallet | Source and destination wallets are the same |
| payout_failed | Payout processing failed |
| refund_failed | Refund processing failed |
| conversion_failed | Currency conversion failed |
| business_not_approved | Business is pending admin approval |
| invalid_user | User not found or does not belong to your business |
| phone_taken | Phone number already in use |
| creation_failed | User creation failed |
| no_consent | User has not granted wallet access |
| code_expired | OTP code has expired |
Transaction Statuses
| Status | Description |
|---|---|
| Pending | Transaction created, awaiting verification |
| Initiated | Transaction initiated with provider |
| Completed | Transaction completed successfully |
| Failed | Transaction failed |
| Cancelled | Transaction cancelled |
| FundsReserved | Funds reserved for transaction |

