Charges
A charge collects money from a customer's MTN or Airtel wallet (cash-in). The customer approves it on their phone.
| Method | Path | |
|---|---|---|
| POST | /v1/charges | Create a charge |
| GET | /v1/charges/:id | Retrieve a charge |
| GET | /v1/charges | List charges |
| GET | /v1/charges/:id/timeline | Charge 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.
currencyenumUGX,KESorGHS.countryenum- The wallet's country:
UG,KEorGH. networkenumMTNorAIRTEL.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. netstringamount − 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. currencyenumRequiredUGX,KESorGHS. Must be the currency of the wallet's country.msisdnstringRequired- The customer's phone number. International format (
+256772123456) is safest; local formats are read againstcountryor 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. networkenumOptionalMTNorAIRTEL. Worked out from the number; send it only when you getnetwork_required, or when you already know it. It always wins over the prefix table.countryenumOptionalUG,KEorGH. 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:
201and the charge: it was created. Checkstatus; it can already beFAILEDif the network refused outright.201again, withIdempotent-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.
countryenumOptionalUG,KE,GHnetworkenumOptionalMTN,AIRTELcreated_afterdatetimeOptional- ISO 8601, inclusive.
created_beforedatetimeOptional- ISO 8601, inclusive.
limitintegerOptional- 1–100, default 25.
starting_afterstringOptional- The
next_cursorof 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.