Utility purchases
Buy airtime and data for a mobile number.
You can buy airtime and data for any mobile number in Ghana, paid from your wallet or by the customer over mobile money.
What you can buy
List the products on sale now:
GET /v1/utility-products
{
"items": [
{
"id": "mtn-airtime",
"category": "airtime",
"network": "mtn",
"name": "MTN airtime",
"kind": "flexi",
"min": "1.00",
"max": "500.00",
"currency": "GHS",
"delivery": "verified"
},
{
"id": "telecel-data-1gb",
"category": "data",
"network": "telecel",
"name": "1GB",
"kind": "fixed",
"price": "10.00",
"currency": "GHS",
"validity": "30 days",
"delivery": "unverified"
}
]
}
The values above only show the shape. Read the list rather than hard-coding product IDs.
- The list has airtime and data only.
- MTN data is sold as flexi data. MTN’s named bundles are not offered.
- Ismart Global products are not offered.
- ECG and other meter products are not on sale yet.
- Telecel and AirtelTigo products show
delivery: unverified. None has been confirmed delivered yet, so check the first ones you sell.
Amounts and fees
- A
fixedproduct is sold at itsprice. Send that as theamount. - A
flexiproduct is sold at any amount frommintomax. - Airtime starts at GHS 1.00.
- The fee is added on top of the amount. You pay the amount plus the fee.
Ask for the fee before you buy:
GET /v1/fee-quotes/utilities?productId=mtn-airtime&amount=10.00¤cy=GHS
{
"productId": "mtn-airtime",
"amount": "10.00",
"currency": "GHS",
"fee": "0.20",
"gross": "10.20"
}
gross is what you pay. A quote holds nothing. The purchase prices again when it runs.
Buy
POST /v1/utility-purchases
Idempotency-Key: 6c1f0e2a-airtime-1
{
"reference": "airtime-1001",
"productId": "mtn-airtime",
"amount": "10.00",
"currency": "GHS",
"beneficiary": { "msisdn": "233244123456" },
"funding": { "type": "wallet" }
}
referenceis yours, and unique for each purchase. See Idempotency and references.beneficiary.msisdnis the number that receives the airtime or data, in233form.currencyisGHS. No other currency is sold.
A 202 means the purchase was accepted. It has not been delivered yet:
{
"reference": "airtime-1001",
"status": "accepted",
"product": { "id": "mtn-airtime", "category": "airtime", "network": "mtn", "name": "MTN airtime" },
"beneficiary": { "msisdn": "233244123456" },
"amount": "10.00",
"currency": "GHS",
"fee": "0.20",
"gross": "10.20",
"funding": { "type": "wallet" },
"createdAt": "2026-10-04T10:00:00Z"
}
Funding
funding.type says who pays.
wallet. The total is taken from your wallet. If the sale fails, it goes back to your wallet. Do not send apayer.collection. The customer pays. They get a mobile money prompt for the total and approve it on their phone. Nothing is sold until they have paid. If the sale then fails, they are refunded in full.
{
"type": "collection",
"payer": { "provider": "mtn", "msisdn": "233244000111" }
}
payer.provider is mtn or telecel.
Statuses
| Status | Meaning | Final? |
|---|---|---|
accepted |
We have the purchase. | No |
awaiting_payment |
Waiting for the customer to approve the mobile money prompt. | No |
vending |
Being sold. | No |
delivered |
The airtime or data was sent. | Yes |
failed |
The sale did not go through. failureReason says why. |
Ends the sale; a refund can follow |
refunding |
The customer’s payment is being returned. | No |
refunded |
The customer’s payment was returned in full. | Yes |
refund_failed |
The refund did not reach the customer. iSmartPay retries it. | No |
outcome_unknown |
The result was not clear. See below. | No |
outcome_unknown means we could not tell whether the airtime or data was sent. The money stays held and nothing is sold again. iSmartPay finds out and moves the purchase on.
Follow a purchase with webhooks, or poll GET /v1/utility-purchases/{reference}. Treat any status you do not know as still in progress.
Permissions
| To | The key needs |
|---|---|
| Quote a fee or buy | disbursements.write |
| List products or read a purchase | disbursements.read |
Webhooks
Subscribe an endpoint to these events in the console:
| Type | Sent when |
|---|---|
utility_purchase.accepted |
A purchase is accepted. |
utility_purchase.status_changed |
A purchase moves to a new status, such as delivered or refunded. |
data is the purchase as it stood when the event was sent. It does not carry the beneficiary’s number or the payer. Fetch the purchase by reference when you need them. See Webhooks.
If your webhook endpoints were set up before utility purchases arrived, edit or add an endpoint in the console once. Until then, these events are not sent to your business, and they do not appear in GET /v1/events.
Errors
As well as the common errors, a purchase or quote can be refused with:
| Status | Code | What to do |
|---|---|---|
| 404 | product_not_found |
No product has that productId. Read the product list again. |
| 422 | product_not_sellable |
The product is not on sale now. Pick another. |
| 422 | amount_out_of_range |
The amount is not the product’s price, or is outside min and max. |
| 422 | currency_not_supported |
Send GHS. |
| 422 | beneficiary_invalid |
The product cannot be sold to this number. Check the network. |
| 422 | fee_unpriceable |
The fee cannot be worked out right now. Try again later. |
| 503 | utilities_unavailable |
Purchases are not available right now. Try again later. |