Payments

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

  1. Initialize - Create a payment request with amount and payer details
  2. Pending - HPN code generated, waiting for customer payment
  3. Processing - Payment detected, being verified
  4. Success/Partial/Overpaid - Payment verified with amount comparison

Endpoints

MethodEndpointDescription
POST/v1/transactions/initializeCreate a new payment request
GET/v1/transactionsList 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}/reconcileManually trigger reconciliation
POST/v1/transactions/{reference}/cancelCancel a payment request
GET/v1/merchant/payment-methodsGet merchant payment methods (for displaying to customers)

Initialize Payment

Create a new payment request and receive an HPN code for the customer.

Plain Text
POST /v1/transactions/initialize

Required scope: transactions:write or transactions:initialize

Request Body

FieldTypeRequiredDescription
amountstringYesPayment amount (e.g., “150.00”)
phone_numberstringYesPayer’s phone number (local or E.164 format)
currencystringNoCurrency code. Default: “GHS”
customer_namestringNoPayer’s name for records
descriptionstringNoTransaction description
webhook_urlstringNoOverride default webhook URL
redirect_urlstringNoURL to redirect customer after payment (for hosted checkout)
idempotency_keystringNoClient-provided deduplication key
client_referencestringNoYour own reference for correlating payments (e.g., order ID). Max 100 chars.
metaobjectNoArbitrary metadata (max 4KB)
send_sms_to_payerbooleanNoSend HPN code via SMS. Default: true
expires_in_hoursintegerNoExpiry time in hours (1-24). Default: 24
expected_telco_transaction_idstringNoExpected telco transaction ID for matching (when customer pays without HPN code). Only works for same-network transfers.
expected_payer_phonestringNoExpected payer phone for anti-spoofing. Payments from other numbers go to manual review.

Example Request

Bash
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

JSON
{
  "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 kxrt5m2pa3 or KXRT5M2PA3
  • 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

  1. Create a payment request with redirect_url set to your success page
  2. Redirect the customer to the checkout_url returned in the response
  3. Customer views payment details and pays via MoMo
  4. After payment, customer is automatically redirected to your redirect_url
  5. 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

JavaScript
// 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/KXRT5M2PA3

Redirect Parameters

After payment, the customer is redirected to your redirect_url with query parameters:

Plain Text
https://mystore.com/order-complete?reference=KXRT5M2PA3&status=success
ParameterDescription
referenceThe HPN code
statusPayment 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.

Plain Text
GET /v1/transactions

Required scope: transactions:read

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number
per_pageinteger50Results per page (max: 100)
statusstring-Filter by status (PENDING, SUCCESS, etc.)
fromstring-Filter from date (ISO 8601)
tostring-Filter to date (ISO 8601)
phone_numberstring-Filter by payer phone number

Example Request

Bash
curl -X GET "https://api.harpoonsms.com/v1/transactions?status=SUCCESS&per_page=10" 
  -H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"

Example Response

JSON
{
  "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.

Plain Text
GET /v1/transactions/{reference}

Required scope: transactions:read

Path Parameters

ParameterDescription
referenceThe transaction reference (e.g., ref_abc123xyz789)

Example Request

Bash
curl -X GET https://api.harpoonsms.com/v1/transactions/ref_abc123xyz789 
  -H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"

Example Response

JSON
{
  "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.

Plain Text
GET /v1/transactions/by-hpn/{hpn_code}

Required scope: transactions:read

Path Parameters

ParameterDescription
hpn_codeThe HPN code (e.g., KXRT5M2PA3)

Example Request

Bash
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).

Plain Text
GET /v1/transactions/by-client-ref/{client_ref}

Required scope: transactions:read

Path Parameters

ParameterDescription
client_refYour client reference (e.g., order_1234)

Example Request

Bash
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.

Plain Text
POST /v1/transactions/{reference}/reconcile

Required scope: transactions:write

Path Parameters

ParameterDescription
referenceThe transaction reference

Request Body

FieldTypeRequiredDescription
transaction_idstringYesThe telco transaction ID from the customer’s MoMo confirmation (e.g. FT254123456789 for MTN)
amountdecimalNoStrict-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.
notesstringNoAdditional context or notes for the reconciliation record

Example Request

Bash
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)

JSON
{
  "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.

JSON
{
  "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

FieldDescription
statusReconciliation outcome — see the table below
new_transaction_statusThe updated state of the payment request: SUCCESS, PARTIAL, or OVERPAID
matched_amountThe actual amount from the telco SMS
expected_amountThe amount originally requested when creating the payment
differenceSigned difference (matched_amount - expected_amount) — negative means underpaid
difference_typeOne of exact, underpaid, overpaid

Reconciliation Statuses

StatusDescription
VERIFIEDPayment verified and transaction updated. Check new_transaction_status to see whether it was exact, partial, or overpaid.
ALREADY_CONFIRMEDTransaction was already completed
NOT_FOUNDNo matching transaction found — check the transaction ID and ensure the payment SMS has been synced
AMOUNT_MISMATCHOnly 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.

Plain Text
POST /v1/transactions/{reference}/cancel

Required scope: transactions:write

Path Parameters

ParameterDescription
referenceThe transaction reference

Request Body

FieldTypeRequiredDescription
reasonstringNoReason for cancellation (recommended for audit trail)

Example Request

Bash
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

JSON
{
  "success": true,
  "reference": "ref_abc123xyz789",
  "status": "CANCELLED",
  "message": "Transaction has been cancelled"
}

Transaction Statuses

StatusDescription
CREATEDPayment request initialized, HPN code generated
PENDINGAwaiting customer payment
PROCESSINGPayment detected, being verified
SUCCESSPayment verified, amount matches expected
PARTIALPayment received but amount is less than expected
OVERPAIDPayment received but amount exceeds expected
EXPIREDPayment request expired without payment
FAILEDPayment verification failed
MANUAL_REVIEWRequires manual review
REFUNDEDPayment was refunded to customer
CANCELLEDTransaction was cancelled

Difference Types

When a payment is verified, the difference_type field indicates how the actual amount compares to the expected amount:

TypeDescription
EXACTActual amount matches expected exactly
UNDERPAIDActual amount is less than expected
OVERPAIDActual amount exceeds expected

Handling Partial Payments

When a customer pays less than the expected amount, the transaction status will be PARTIAL:

JSON
{
  "reference": "ref_abc123xyz789",
  "status": "PARTIAL",
  "expected_amount": "150.00",
  "actual_amount": "100.00",
  "difference": "50.00",
  "difference_type": "UNDERPAID"
}

You can either:

  1. Accept the partial payment and fulfill partially
  2. Request the customer to send the remaining amount
  3. 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.

Bash
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.

Plain Text
GET /v1/merchant/payment-methods

Required scope: transactions:read or any valid API key

Example Request

Bash
curl -X GET https://api.harpoonsms.com/v1/merchant/payment-methods 
  -H "Authorization: Bearer hpn_live_sk_xxxxxxxxxxxxx"

Example Response

JSON
{
  "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

FieldTypeDescription
providerstringProvider identifier (e.g., “momo_mtn”)
provider_namestringHuman-readable provider name (e.g., “MTN MoMo”)
phone_numberstringPhone number to send payment to (display format)
merchant_codestring | nullProvider-specific merchant/agent code (e.g., 6-digit MTN merchant code). May be null if not configured.
account_namestringAccount holder name
short_codestringUSSD shortcode for this provider
instructionsarrayStep-by-step payment instructions with template variables

Instruction Template Variables

The instructions array contains template variables that should be replaced when displaying to customers:

VariableDescription
{phone}The merchant’s phone number for this provider
{amount}The payment amount
{hpn_code}The HPN code from the initialize response

Next Steps