Payments, in your code.

A REST API and signed webhooks for verifying Mobile Money payments. Integrate in an afternoon. Ship with confidence.

One API.
Every network.

MTN MoMo. Telecel Cash. AT Money. A single endpoint handles all three. You write the integration once and never think about it again.

Initialize a payment
in four lines.

Send the amount and a reference. Get back a short code your customer can pay to. We take it from there.

initialize.py
import requests

res = requests.post(
    'https://api.harpoonsms.com/v1/payments/initialize',
    headers={'Authorization': f'Bearer {API_KEY}'},
    json={
        'amount': 250.00,
        'currency': 'GHS',
        'reference': 'order_8821',
        'customer_phone': '+233241234567',
    },
)

payment = res.json()
# payment['hpn_code']   -> 'HKMVR2'
# payment['status']     -> 'pending'
# payment['expires_at'] -> '2026-04-10T12:00:00Z'

Everything you need.
Nothing you don't.

REST API

A small, predictable set of endpoints. JSON in, JSON out. Idempotent by default.

Signed Webhooks

HMAC-SHA256 signatures on every payload. Exponential backoff on retries. At-least-once delivery.

Scoped API Keys

Separate keys for live and test. Scoped permissions per key. Rotate without downtime.

Works with
your stack.

If your language can speak HTTP, it can speak Harpoon. No SDKs to install. No lock-in.

PythonNode.jsGoRubyPHPRustJava.NET

Webhooks that
keep their promises.

When a payment lands, we tell you. Signed with HMAC-SHA256. Retried with exponential backoff across eight attempts. Delivered until acknowledged.

payment.succeeded
payment.partial
payment.overpaid
payment.expired
payment.failed
payment.succeeded
{
  "event": "payment.succeeded",
  "created_at": "2026-04-09T14:22:08Z",
  "data": {
    "hpn_code": "HKMVR2",
    "reference": "order_8821",
    "amount": 250.00,
    "currency": "GHS",
    "network": "mtn",
    "payer_phone": "+233241234567",
    "verified_at": "2026-04-09T14:22:06Z"
  }
}
verify.js
const res = await fetch(
  `https://api.harpoonsms.com/v1/payments/${ref}`,
  { headers: { Authorization: `Bearer ${API_KEY}` } }
);

const payment = await res.json();

if (payment.status === 'succeeded') {
  await fulfillOrder(payment.reference);
}

Verify on demand.

Prefer polling to webhooks? Query any payment by reference or HPN code. The response tells you exactly where it stands — pending, succeeded, partial, overpaid, or expired.

Secure by default.

Every request authenticated. Every webhook signed. Every key scoped. No exceptions.

HMAC signatures

Every webhook is signed with SHA-256. Verify in constant time, reject the rest.

Key rotation

Generate new keys, overlap the old, revoke on your schedule. Zero downtime.

Rate limiting

Predictable limits with clear headers. Burst-friendly, abuse-resistant.

Ready to
integrate?

Grab an API key. Read the docs. Ship before lunch.