Skip to content
SkanPay Docs

Charges

A charge collects money from a customer's MTN or Airtel wallet (cash-in). The customer approves it on their phone.

MethodPath
POST/v1/chargesCreate a charge
GET/v1/charges/:idRetrieve a charge
GET/v1/chargesList charges
GET/v1/charges/:id/timelineCharge timeline

The charge object

{
  "id": "chg_01J9Z3K8Q4W6XK2M7N5P0R3T8V",
  "object": "charge",
  "status": "SUCCESSFUL",
  "amount": "50000",
  "currency": "UGX",
  "country": "UG",
  "network": "MTN",
  "msisdn_masked": "+2567****456",
  "reference": "order-9001",
  "description": "2 x T-shirt",
  "fee": "1250",
  "net": "48750",
  "provider": "…",
  "failure_code": null,
  "failure_message": null,
  "metadata": { "cart_id": "c_42" },
  "created_at": "2026-09-28T09:29:51.000Z",
  "updated_at": "2026-09-28T09:30:08.000Z"
}

Attributes

idstring
Unique id, prefixed chg_.
objectstring
Always "charge".
statusenum
See Transaction statuses.
amountstring
What the customer pays, in minor units.
currencyenum
UGX, KES or GHS.
countryenum
The wallet's country: UG, KE or GH.
networkenum
MTN or AIRTEL.
msisdn_maskedstring
The wallet number, masked (+2567****456). Full numbers are never returned.
referencestring
Your reference, unchanged.
descriptionstring | null
As sent.
feestring
SkanPay's fee, in minor units. "0" until the charge succeeds.
netstring
amount − fee: what is credited to your balance.
providerstring | null
An identifier for the rail that carried the payment. Informational: don't build logic on it.
failure_codestring | null
Why it failed. See failure codes.
failure_messagestring | null
Human-readable reason. Don't parse it.
metadataobject | null
As sent.
created_atstring
ISO 8601, UTC.
updated_atstring
ISO 8601, UTC. Changes with the status.

Create a charge

POST/v1/charges

Sends a payment prompt to the customer's phone. Returns 201 with the charge, normally in status AWAITING_CUSTOMER. The outcome arrives by webhook.

Headers

Idempotency-KeyheaderRequired
8–255 characters. Same key and body returns the original response. See Idempotency.

Body parameters

amountstringRequired
Integer minor units as a string, e.g. "50000". See Amounts.
currencyenumRequired
UGX, KES or GHS. Must be the currency of the wallet's country.
msisdnstringRequired
The customer's phone number. International format (+256772123456) is safest; local formats are read against country or your account's country. See Phone numbers.
referencestringRequired
Your own id for this payment, e.g. your order id. 1–128 characters from A–Z a–z 0–9 . _ : -. Returned unchanged everywhere.
networkenumOptional
MTN or AIRTEL. Worked out from the number; send it only when you get network_required, or when you already know it. It always wins over the prefix table.
countryenumOptional
UG, KE or GH. Read from the number's dialing code; used only to interpret a number in local format. Defaults to your account's country.
descriptionstringOptional
Up to 255 characters, for your own records.
metadataobjectOptional
Up to 20 keys of your own data. Values are strings (≤ 512 characters), numbers, booleans or null.
curl -X POST https://api.skanpay.website/v1/charges \
  -H "Authorization: Bearer $SKANPAY_SECRET_KEY" \
  -H "Idempotency-Key: order-9001" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "50000",
    "currency": "UGX",
    "msisdn": "+256772123456",
    "reference": "order-9001",
    "description": "2 x T-shirt",
    "metadata": { "cart_id": "c_42" }
  }'

Returns:

  • 201 and the charge: it was created. Check status; it can already be FAILED if the network refused outright.
  • 201 again, with Idempotent-Replayed: true, for a retry with the same key and body.
  • An error: nothing was created and nothing reached the customer.

Retrieve a charge

GET/v1/charges/:id

curl https://api.skanpay.website/v1/charges/chg_01J9Z3K8Q4W6XK2M7N5P0R3T8V \
  -H "Authorization: Bearer $SKANPAY_SECRET_KEY"

Returns the charge, or 404 charge_not_found.

List charges

GET/v1/charges

Newest first, cursor-paginated. Unknown query parameters are rejected.

Query parameters

statusenumOptional
One of the statuses.
referencestringOptional
Exact match on your reference.
countryenumOptional
UG, KE, GH
networkenumOptional
MTN, AIRTEL
created_afterdatetimeOptional
ISO 8601, inclusive.
created_beforedatetimeOptional
ISO 8601, inclusive.
limitintegerOptional
1–100, default 25.
starting_afterstringOptional
The next_cursor of the previous page.
curl "https://api.skanpay.website/v1/charges?status=SUCCESSFUL&created_after=2026-09-01T00:00:00Z&limit=50" \
  -H "Authorization: Bearer $SKANPAY_SECRET_KEY"

Charge timeline

GET/v1/charges/:id/timeline

The charge with every status event and every ledger entry behind it: the same trail SkanPay support sees. Use it to settle disputes. The shape matches GET /v1/transactions/:id, with the object under charge.

{
  "charge": { "id": "chg_…", "object": "charge", "status": "SUCCESSFUL", ... },
  "events": [
    { "id": "evt_…", "status": "INITIATED",         "source": "API", "provider_status": null, "message": null, "created_at": "…" },
    { "id": "evt_…", "status": "PENDING_PROVIDER",  "source": "API", "provider_status": null, "message": "…", "created_at": "…" },
    { "id": "evt_…", "status": "AWAITING_CUSTOMER", "source": "API", "provider_status": "…", "message": null, "created_at": "…" },
    { "id": "evt_…", "status": "SUCCESSFUL", "source": "PROVIDER_CALLBACK", "provider_status": "…", "message": null, "created_at": "…" }
  ],
  "ledger": [
    { "id": "…", "journal_id": "…", "account": "PROVIDER_FLOAT", "side": "DEBIT", "amount": "50000", "currency": "UGX", "reason": "CHARGE_SETTLED", "created_at": "…" },
    ...
  ]
}

provider_status is the network's own status word, kept for audit; branch only on status. See sources and Reconciliation.