Response format & statuses
Responses are plain JSON objects with no wrapper. Every purchase returns the same transaction object, and its status is always successful, pending or failed.
Purchase statuses
| Status | HTTP | What it means | What to do |
|---|---|---|---|
| successful | 201 | Delivered. token, pins, delivery or units are included when relevant. | Mark your order complete. |
| pending | 202 | The provider is still processing. Your wallet has been debited. | Do NOT retry. Wait for the webhook or poll GET /transactions/{reference}. |
| failed | 422 | Not delivered. Your wallet was refunded. The body includes code: TRANSACTION_FAILED. | Show the message; retry with a NEW reference if you want. |
Idempotency and duplicate references
- Re-sending a
referenceyou already used returns the original transaction withcode: DUPLICATEandduplicate: true— you are never charged twice. - HTTP 200 if the original succeeded, 202 if it is still pending, 409 if it failed.
- If a request timed out and GET /transactions/{reference} returns 404, the purchase never reached us and it is safe to retry with the same reference.
Transaction object
Empty or null fields are omitted. Money values are strings with two decimals (e.g. "480.00").
| Field | Type | Description |
|---|---|---|
| status | string | successful · pending · failed |
| reference | string | Your reference, exactly as you sent it |
| channel | string | Where the purchase was made: api, web or app |
| transaction_id | string | FinalDataSub's own transaction reference |
| category | string | data, airtime, electricity, cable, education, betting, internet, voice, bulk_sms, rechargepin, giftcard, esim |
| message | string | Human-readable result |
| network | string | Network (airtime, data and recharge PINs only) |
| biller / biller_slug | string | Biller name and slug |
| product_id / product | integer / string | Plan id and name |
| plan | string | Plan name (data only) |
| phone | string | Recipient phone number |
| customer_ref / customer_name | string | Phone, meter, smartcard or account number, and the verified name |
| quantity / unit_amount | integer / string | Units bought (PINs, cards, SMS pages × recipients) and price per unit |
| amount | string | What you paid before fees |
| face_amount | string | Face value of the service |
| fee / discount / cashback | string | Fee charged, discount applied, cashback credited |
| total_deducted | string | Total debited from your wallet |
| balance_before / balance_after | string | Wallet balance around this transaction |
| provider_reference | string | Provider reference (successful only) |
| token / units | string | Electricity token and units |
| purchased_at / funded_at | string | ISO-8601 timestamp (funded_at for betting) |
| delivery | array | Gift card codes / eSIM activation details (successful only) |
| pin_order_id / name_on_card | integer / string | PIN order created for PIN purchases (successful only) |
| pins / serial_numbers / cards | array | Exam PINs: list of PINs, serials and {pin, serial} cards |
| denomination / total_amount / pins | string / string / array | Recharge PINs: face value, order total and {pin, serial, load_code} items |
Examples
{
"status": "successful",
"reference": "ORDER-10021",
"channel": "api",
"transaction_id": "DS261005101522K7Q2ZP4M",
"category": "data",
"message": "Transaction successful.",
"network": "MTN",
"biller": "MTN Data",
"biller_slug": "mtn-data",
"product_id": 212,
"product": "MTN SME 1GB - 30 Days",
"plan": "MTN SME 1GB - 30 Days",
"phone": "08031234567",
"customer_ref": "08031234567",
"quantity": 1,
"unit_amount": "480.00",
"amount": "480.00",
"face_amount": "500.00",
"fee": "0.00",
"discount": "20.00",
"total_deducted": "480.00",
"balance_before": "25000.00",
"balance_after": "24520.00",
"provider_reference": "PRV-88213409",
"purchased_at": "2026-10-05T10:15:22+01:00"
}