Payments API
Create payment requests and verify MoMo transactions automatically.
Overview
The Payments API allows you to create payment requests, track their status, and verify when customers have paid. Harpoon uses HPN codes - unique reference codes that customers include when sending MoMo payments for automatic verification.
Payment Lifecycle
- Initialize - Create a payment request with amount and payer details
- Pending - HPN code generated, waiting for customer payment
- Processing - Payment detected, being verified
- Success/Partial/Overpaid - Payment verified with amount comparison
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/transactions/initialize | Create a new payment request |
| GET | /v1/transactions | List all transactions |
| GET | /v1/transactions/{reference} | Get transaction by reference |
| GET | /v1/transactions/by-hpn/{hpn_code} | Get transaction by HPN code |
| GET | /v1/transactions/by-client-ref/{client_ref} | Get transaction by client reference |
| POST | /v1/transactions/{reference}/reconcile | Manually trigger reconciliation |
| POST | /v1/transactions/{reference}/cancel | Cancel a payment request |
| GET | /v1/merchant/payment-methods | Get merchant payment methods (for displaying to customers) |
Initialize Payment
Create a new payment request and receive an HPN code for the customer.
POST /v1/transactions/initializeRequired scope: transactions:write or transactions:initialize
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
amount | string | Yes | Payment amount (e.g., “150.00”) |
phone_number | string | Yes | Payer’s phone number (local or E.164 format) |
currency | string | No | Currency code. Default: “GHS” |
customer_name | string | No | Payer’s name for records |
description | string | No | Transaction description |
webhook_url | string | No | Override default webhook URL |
redirect_url | string | No | URL to redirect customer after payment (for hosted checkout) |
idempotency_key | string | No | Client-provided deduplication key |
client_reference | string | No | Your own reference for correlating payments (e.g., order ID). Max 100 chars. |
meta | object | No | Arbitrary metadata (max 4KB) |
send_sms_to_payer | boolean | No | Send HPN code via SMS. Default: true |
expires_in_hours | integer | No | Expiry time in hours (1-24). Default: 24 |
expected_telco_transaction_id | string | No | Expected telco transaction ID for matching (when customer pays without HPN code). Only works for same-network transfers. |
expected_payer_phone | string | No | Expected payer phone for anti-spoofing. Payments from other numbers go to manual review. |
Example Request
curl -X POST https://api.harpoonsms.com/v1/transactions/initialize
-H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"
-H "Content-Type: application/json"
-d '{
"amount": "150.00",
"phone_number": "0244123456",
"currency": "GHS",
"customer_name": "John Doe",
"description": "Order #1234",
"client_reference": "order_1234",
"meta": {
"order_id": "1234",
"customer_email": "[email protected]"
}
}'Example Response
{
"success": true,
"data": {
"reference": "ref_abc123xyz789",
"hpn_code": "KXRT5M2PA3",
"amount": "150.00",
"currency": "GHS",
"status": "PENDING",
"expires_at": "2024-01-09T12:00:00Z",
"created_at": "2024-01-08T12:00:00Z",
"payer_sms_sent": true,
"checkout_url": "https://harpoonsms.com/pay/KXRT5M2PA3",
"client_reference": "order_1234"
},
"meta": {
"request_id": "req_abc123xyz",
"idempotency_key": null
}
}HPN Codes
HPN codes are unique reference codes that customers include when sending MoMo payments. They are:
- Case-insensitive - customers can enter
kxrt5m2pa3orKXRT5M2PA3 - Easy to read and type - designed to avoid confusion between similar characters
- Time-limited - expire based on
expires_in_hours
Hosted Checkout
Harpoon provides a hosted payment page that you can redirect customers to. This is the easiest way to integrate payments - no need to build your own payment UI.
How It Works
- Create a payment request with
redirect_urlset to your success page - Redirect the customer to the
checkout_urlreturned in the response - Customer views payment details and pays via MoMo
- After payment, customer is automatically redirected to your
redirect_url - Your webhook receives the payment notification
Checkout Page Features
- Payment instructions for all supported providers
- Real-time status updates
- Mobile-optimized design
- Automatic redirect on successful payment
Example: Checkout Integration
// Backend: Create payment and get checkout URL
const response = await fetch('https://api.harpoonsms.com/v1/transactions/initialize', {
method: 'POST',
headers: {
'Authorization': 'Bearer hpn_live_sk_xxxxxxxxxxxxx',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: '150.00',
phone_number: '0244123456',
customer_name: 'John Doe',
description: 'Order #1234',
redirect_url: 'https://mystore.com/order-complete',
webhook_url: 'https://mystore.com/api/webhook'
})
});
const { data } = await response.json();
// Frontend: Redirect customer to checkout
window.location.href = data.checkout_url;
// Redirects to: https://harpoonsms.com/pay/KXRT5M2PA3Redirect Parameters
After payment, the customer is redirected to your redirect_url with query parameters:
https://mystore.com/order-complete?reference=KXRT5M2PA3&status=success| Parameter | Description |
|---|---|
reference | The HPN code |
status | Payment status (success, partial, overpaid) |
Note: Verify payment status via webhook or API before fulfilling orders. Redirect parameters can be manipulated.
List Transactions
Retrieve a paginated list of all transactions.
GET /v1/transactionsRequired scope: transactions:read
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
per_page | integer | 50 | Results per page (max: 100) |
status | string | - | Filter by status (PENDING, SUCCESS, etc.) |
from | string | - | Filter from date (ISO 8601) |
to | string | - | Filter to date (ISO 8601) |
phone_number | string | - | Filter by payer phone number |
Example Request
curl -X GET "https://api.harpoonsms.com/v1/transactions?status=SUCCESS&per_page=10"
-H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"Example Response
{
"success": true,
"data": [
{
"reference": "ref_abc123xyz789",
"hpn_code": "KXRT5M2PA3",
"status": "SUCCESS",
"expected_amount": "150.00",
"actual_amount": "150.00",
"difference": null,
"difference_type": "EXACT",
"currency": "GHS",
"payer_phone": "+233244123456",
"payer_name": "JOHN DOE",
"created_at": "2024-01-08T12:00:00Z",
"completed_at": "2024-01-08T12:05:32Z"
}
],
"pagination": {
"page": 1,
"per_page": 10,
"total": 156,
"total_pages": 16,
"has_next": true,
"has_prev": false
}
}Get Transaction by Reference
Retrieve a specific transaction by its reference.
GET /v1/transactions/{reference}Required scope: transactions:read
Path Parameters
| Parameter | Description |
|---|---|
reference | The transaction reference (e.g., ref_abc123xyz789) |
Example Request
curl -X GET https://api.harpoonsms.com/v1/transactions/ref_abc123xyz789
-H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"Example Response
{
"success": true,
"data": {
"reference": "ref_abc123xyz789",
"hpn_code": "KXRT5M2PA3",
"status": "SUCCESS",
"expected_amount": "150.00",
"actual_amount": "150.00",
"difference": null,
"difference_type": "EXACT",
"currency": "GHS",
"payer": {
"phone_number": "+233244123456",
"name": "JOHN DOE"
},
"telco": {
"provider": "mtn",
"transaction_id": "1234567890",
"timestamp": "2024-01-08T12:05:30Z"
},
"meta": {
"order_id": "1234",
"customer_email": "[email protected]"
},
"timeline": {
"created_at": "2024-01-08T12:00:00Z",
"pending_at": "2024-01-08T12:00:01Z",
"processing_at": "2024-01-08T12:05:30Z",
"completed_at": "2024-01-08T12:05:32Z"
},
"processing_time_ms": 2000,
"client_reference": "order_1234"
}
}Get Transaction by HPN Code
Look up a transaction using its HPN code.
GET /v1/transactions/by-hpn/{hpn_code}Required scope: transactions:read
Path Parameters
| Parameter | Description |
|---|---|
hpn_code | The HPN code (e.g., KXRT5M2PA3) |
Example Request
curl -X GET https://api.harpoonsms.com/v1/transactions/by-hpn/KXRT5M2PA3
-H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"Example Response
Same as Get Transaction by Reference.
Get Transaction by Client Reference
Look up a transaction using your own client reference (e.g., order ID).
GET /v1/transactions/by-client-ref/{client_ref}Required scope: transactions:read
Path Parameters
| Parameter | Description |
|---|---|
client_ref | Your client reference (e.g., order_1234) |
Example Request
curl -X GET https://api.harpoonsms.com/v1/transactions/by-client-ref/order_1234
-H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"Example Response
Same as Get Transaction by Reference.
Manual Reconciliation
Manually reconcile a payment using the telco transaction ID from the customer’s MoMo confirmation message. Use this when automatic verification didn’t occur (e.g., customer didn’t include the HPN code).
How it works: Ask the customer for their transaction ID from their MoMo confirmation SMS, then submit it to this endpoint. Harpoon matches it against synced transactions and updates the payment status.
POST /v1/transactions/{reference}/reconcileRequired scope: transactions:write
Path Parameters
| Parameter | Description |
|---|---|
reference | The transaction reference |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
transaction_id | string | Yes | The telco transaction ID from the customer’s MoMo confirmation (e.g. FT254123456789 for MTN) |
amount | decimal | No | Strict-check opt-in: if supplied, the server rejects with AMOUNT_MISMATCH unless this matches the telco amount exactly. Omit to accept whatever the telco confirms — the payment will be classified as SUCCESS, PARTIAL, or OVERPAID based on the actual amount. |
notes | string | No | Additional context or notes for the reconciliation record |
Example Request
curl -X POST https://api.harpoonsms.com/v1/transactions/ref_abc123xyz789/reconcile
-H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"
-H "Content-Type: application/json"
-d '{
"transaction_id": "FT254123456789",
"notes": "Customer provided transaction ID via WhatsApp"
}'Example Response (Success)
{
"success": true,
"data": {
"reconciliation_id": "rec_abc123xyz",
"status": "VERIFIED",
"transaction_id": "FT254123456789",
"transaction_updated": true,
"new_transaction_status": "SUCCESS",
"matched_amount": "150.00",
"expected_amount": "150.00",
"difference": "0.00",
"difference_type": "exact",
"message": "Payment verified successfully"
}
}Example Response (Partial Payment)
When the customer paid less than the expected amount, the payment is still verified but classified as partial. Read new_transaction_status and difference to decide how to proceed.
{
"success": true,
"data": {
"reconciliation_id": "rec_def456",
"status": "VERIFIED",
"transaction_id": "FT254000000001",
"transaction_updated": true,
"new_transaction_status": "PARTIAL",
"matched_amount": "100.00",
"expected_amount": "150.00",
"difference": "-50.00",
"difference_type": "underpaid",
"message": "Payment verified as partial: GHS 100.00 of GHS 150.00 expected (short by GHS 50.00)"
}
}Response Fields
| Field | Description |
|---|---|
status | Reconciliation outcome — see the table below |
new_transaction_status | The updated state of the payment request: SUCCESS, PARTIAL, or OVERPAID |
matched_amount | The actual amount from the telco SMS |
expected_amount | The amount originally requested when creating the payment |
difference | Signed difference (matched_amount - expected_amount) — negative means underpaid |
difference_type | One of exact, underpaid, overpaid |
Reconciliation Statuses
| Status | Description |
|---|---|
VERIFIED | Payment verified and transaction updated. Check new_transaction_status to see whether it was exact, partial, or overpaid. |
ALREADY_CONFIRMED | Transaction was already completed |
NOT_FOUND | No matching transaction found — check the transaction ID and ensure the payment SMS has been synced |
AMOUNT_MISMATCH | Only returned when you supplied an amount in the request and it doesn’t match the telco amount exactly. If you omit amount, reconciliation will never fail for this reason — partial and overpaid payments are classified via new_transaction_status instead. |
Cancel Payment
Cancel a payment request that is no longer needed. Use this when orders are cancelled, customers change their mind, or the payment is no longer required.
POST /v1/transactions/{reference}/cancelRequired scope: transactions:write
Path Parameters
| Parameter | Description |
|---|---|
reference | The transaction reference |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | No | Reason for cancellation (recommended for audit trail) |
Example Request
curl -X POST https://api.harpoonsms.com/v1/transactions/ref_abc123xyz789/cancel
-H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"
-H "Content-Type: application/json"
-d '{
"reason": "Customer cancelled order"
}'Example Response
{
"success": true,
"reference": "ref_abc123xyz789",
"status": "CANCELLED",
"message": "Transaction has been cancelled"
}Transaction Statuses
| Status | Description |
|---|---|
CREATED | Payment request initialized, HPN code generated |
PENDING | Awaiting customer payment |
PROCESSING | Payment detected, being verified |
SUCCESS | Payment verified, amount matches expected |
PARTIAL | Payment received but amount is less than expected |
OVERPAID | Payment received but amount exceeds expected |
EXPIRED | Payment request expired without payment |
FAILED | Payment verification failed |
MANUAL_REVIEW | Requires manual review |
REFUNDED | Payment was refunded to customer |
CANCELLED | Transaction was cancelled |
Difference Types
When a payment is verified, the difference_type field indicates how the actual amount compares to the expected amount:
| Type | Description |
|---|---|
EXACT | Actual amount matches expected exactly |
UNDERPAID | Actual amount is less than expected |
OVERPAID | Actual amount exceeds expected |
Handling Partial Payments
When a customer pays less than the expected amount, the transaction status will be PARTIAL:
{
"reference": "ref_abc123xyz789",
"status": "PARTIAL",
"expected_amount": "150.00",
"actual_amount": "100.00",
"difference": "50.00",
"difference_type": "UNDERPAID"
}You can either:
- Accept the partial payment and fulfill partially
- Request the customer to send the remaining amount
- Refund and create a new payment request
Idempotency
Use the idempotency_key parameter to prevent duplicate payment requests. If you send the same idempotency key within 24 hours, you’ll receive the original payment request instead of creating a new one.
curl -X POST https://api.harpoonsms.com/v1/transactions/initialize
-H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"
-H "Content-Type: application/json"
-d '{
"amount": "150.00",
"phone_number": "0244123456",
"idempotency_key": "order_1234_payment"
}'Get Payment Methods
Retrieve your configured payment methods (MoMo numbers) with step-by-step instructions for customers. Use this to display payment options in your checkout flow. Tip: This response can be cached as payment methods rarely change.
GET /v1/merchant/payment-methodsRequired scope: transactions:read or any valid API key
Example Request
curl -X GET https://api.harpoonsms.com/v1/merchant/payment-methods
-H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"Example Response
{
"success": true,
"data": [
{
"provider": "momo_mtn",
"provider_name": "MTN MoMo",
"phone_number": "0241234567",
"merchant_code": "123456",
"account_name": "Kofi's Shop",
"short_code": "*170#",
"instructions": [
"Dial *170#",
"Select 'Transfer Money'",
"Choose 'MoMo User'",
"Enter phone number: {phone}",
"Enter amount: {amount}",
"Enter reference: {hpn_code}",
"Confirm with your PIN"
]
},
{
"provider": "momo_telecel",
"provider_name": "Telecel Cash",
"phone_number": "0201234567",
"merchant_code": null,
"account_name": "Kofi's Shop",
"short_code": "*110#",
"instructions": [
"Dial *110#",
"Select 'Send Money'",
"Choose 'Telecel User'",
"Enter phone number: {phone}",
"Enter amount: {amount}",
"Enter reference: {hpn_code}",
"Confirm with your PIN"
]
}
]
}Response Fields
| Field | Type | Description |
|---|---|---|
provider | string | Provider identifier (e.g., “momo_mtn”) |
provider_name | string | Human-readable provider name (e.g., “MTN MoMo”) |
phone_number | string | Phone number to send payment to (display format) |
merchant_code | string | null | Provider-specific merchant/agent code (e.g., 6-digit MTN merchant code). May be null if not configured. |
account_name | string | Account holder name |
short_code | string | USSD shortcode for this provider |
instructions | array | Step-by-step payment instructions with template variables |
Instruction Template Variables
The instructions array contains template variables that should be replaced when displaying to customers:
| Variable | Description |
|---|---|
{phone} | The merchant’s phone number for this provider |
{amount} | The payment amount |
{hpn_code} | The HPN code from the initialize response |
Next Steps
- Set up webhooks - Receive real-time payment notifications
- Handle errors - Understand error codes and responses