Qwiktop
Sell airtime, data, and bills through Qwiktop.
This guide explains how to connect your website, mobile app, POS, or reseller system to Qwiktop using a secure API key.
Table of Contents
1. What the API can do
The Qwiktop API exposes VTU operations and identity verification:
| Feature | Purpose |
|---|---|
| Check balance | Know if your Qwiktop wallet can cover orders before sending them. |
| Fund wallet | Generate a Paystack checkout link for wallet funding. |
| List data plans | Show live Qwiktop plans and prices in your own app. |
| List airtime networks | Show supported airtime networks and reseller discount. |
| List bill providers | Show electricity and cable TV providers. |
| Place orders | Buy data, buy airtime, or pay bills using wallet balance. |
| Track orders | Check one transaction using its reference. |
| Verify identities and accounts | Check a Ghana Card (NIA), voter ID, bank account, Mobile Money wallet or phone number. See section 7. |
2. Authentication
Every request must include your Qwiktop API key in the X-API-Key header.
curl "https://qwiktop.com/api/v1/balance" \ -H "X-API-Key: qt_your_api_key"
| Header | Required | Description |
|---|---|---|
| X-API-Key | Yes | Your active API key from the Developer page. |
| Content-Type | For POST | Use application/json when sending a request body. |
3. Balance and wallet funding
/balanceReturns wallet, profit, and referral balances for the account that owns the API key.
{
"status": "success",
"data": {
"wallet_balance": 250.00,
"profit_balance": 12.50,
"referral_balance": 5.00,
"currency": "GHS"
}
}
/wallet/fundCreates a Paystack checkout session for funding the wallet.
| Field | Type | Required | Description |
|---|---|---|---|
| amount | number | Yes | Amount in GHS. |
{
"amount": 100
}
{
"status": "success",
"message": "Wallet funding checkout initialized.",
"data": {
"reference": "API-FUND-...",
"authorization_url": "https://checkout.paystack.com/...",
"amount": 100,
"currency": "GHS"
}
}
4. Product catalogues
/data-plans?network=mtnReturns active data plans. Prices automatically use customer or reseller pricing based on the API key owner role.
| Query | Required | Accepted values |
|---|---|---|
| network | No | mtn, telecel, airteltigo, big-time |
{
"status": "success",
"data": [
{
"id": 11,
"network": "mtn",
"name": "MTN Monthly 1GB",
"size": "1GB",
"validity": "Bundle",
"price": 3.70,
"currency": "GHS"
}
]
}
/airtime/networksReturns supported airtime networks and account discount.
/bill-providersReturns bill categories and providers for electricity and cable TV.
5. Place orders
/buy-data| Field | Type | Required | Description |
|---|---|---|---|
| network | string | Yes | mtn, telecel, airteltigo, or big-time. |
| plan_id | integer | Yes | Plan ID from /data-plans. |
| phone | string | Yes | Recipient phone number, e.g. 0241234567. |
/buy-airtime| Field | Type | Required | Description |
|---|---|---|---|
| network | string | Yes | mtn, telecel, or airteltigo. |
| amount | number | Yes | Airtime face value in GHS. |
| phone | string | Yes | Recipient phone number. |
/pay-bill| Field | Type | Required | Description |
|---|---|---|---|
| type | string | Yes | electricity or cable. |
| provider | string | Yes | ecg, nedco, dstv, gotv, or startimes. |
| account | string | Yes | Meter number, smartcard number, or account ID. |
| amount | number | Yes | Amount in GHS. |
6. Transaction status check
/transactions/{reference}Use the reference returned after an order to check the latest status.
{
"status": "success",
"data": {
"reference": "QT-...",
"type": "data",
"service": "MTN_DATA",
"status": "pending",
"amount": 8.50,
"recipient": "0241234567",
"network": "mtn",
"value": "1GB",
"message": "Transaction queued",
"created_at": "2026-09-25T12:00:00+00:00",
"updated_at": "2026-09-25T12:00:05+00:00"
}
}
7. Identity and account verification
Confirm who is behind a Ghana Card, voter ID, bank account, Mobile Money wallet or phone number before you sell to them, pay them or onboard them. Each lookup is charged to your Qwiktop wallet. Qwiktop sets the price for each service and can switch a service on or off, so ask Qwiktop for current pricing and availability.
| Service | Endpoint | What it confirms |
|---|---|---|
| Ghana Card (NIA) | POST /verify/ghana-card | The card number matches the name, gender and date of birth given. |
| Voter ID | POST /verify/voter-id | The voter ID matches the name, sex and date of birth given. |
| Bank account | GET /verify/bank | The name on a bank account. |
| Mobile Money | GET /verify/momo | The name on a Mobile Money wallet. |
| Phone subscriber | GET /verify/msisdn | The registered name of a phone number. |
| MTN identity | GET /verify/mtn | The identity linked to an MTN number. |
Phone numbers
Send a phone number as 0241234567, 233241234567 or +233241234567. Qwiktop treats them as the same number.
Billing, refunds and repeat lookups
- Your wallet is charged when a lookup starts. If it cannot be completed (a 422 or 502 response), the charge is refunded automatically and you are not billed.
- If your account repeats an identical lookup within the retention period, Qwiktop answers from its own saved result without contacting anyone. meta.cached is true, and meta.amount_charged shows what, if anything, you were billed.
- Every successful response carries a meta object. Keep meta.reference for support queries.
- Every call counts towards your API key's daily limit.
Response format
{
"status": "success",
"message": "Verified successfully",
"data": { ... the verification result ... },
"meta": {
"reference": "VERIFY-3F2A9C1E-...",
"cached": false,
"amount_charged": 2.50,
"currency": "GHS"
}
}
The data object holds the result for that lookup. It typically includes the holder's name, and its exact fields depend on the service.
/verify/ghana-card| Field | Type | Required | Description |
|---|---|---|---|
| card_number | string | Yes | Ghana Card number, e.g. GHA-123456789-0. |
| surname | string | Yes | Surname exactly as on the card. |
| first_names | string | Yes | First and middle names as on the card. |
| gender | string | Yes | Male or Female. |
| date_of_birth | string | Yes | YYYY-MM-DD. |
{
"card_number": "GHA-123456789-0",
"surname": "Mensah",
"first_names": "Ama Serwaa",
"gender": "Female",
"date_of_birth": "1990-01-31"
}
/verify/voter-id| Field | Type | Required | Description |
|---|---|---|---|
| voter_id | string | Yes | Voter ID number (8 to 15 characters). |
| surname | string | Yes | Surname as on the voter card. |
| other_names | string | Yes | First and other names as on the voter card. |
| sex | string | Yes | Male or Female. |
| date_of_birth | string | Yes | YYYY-MM-DD. |
/verify/bank?bank_code=030100&account_number=1234567890| Field | Type | Required | Description |
|---|---|---|---|
| bank_code | string | Yes | The bank's code. |
| account_number | string | Yes | Account number (8 to 20 characters). |
/verify/momo?phone_number=0241234567&network=mtn| Field | Type | Required | Description |
|---|---|---|---|
| phone_number | string | Yes | The Mobile Money number. |
| network | string | Yes | mtn, telecel (or vodafone), airteltigo (or tigo). |
/verify/msisdn?phone_number=0241234567| Field | Type | Required | Description |
|---|---|---|---|
| phone_number | string | Yes | The phone number to look up. |
{
"status": "success",
"message": "Phone number verified successfully",
"data": {
"ResponseCode": "0000",
"Message": "Customer Details",
"Label": "Customer Details",
"Data": [{ "Display": "name", "Value": "AMA SERWAA MENSAH", "Amount": 0 }]
},
"meta": {
"reference": "VERIFY-3F2A9C1E-...",
"cached": false,
"amount_charged": 2.50,
"currency": "GHS"
}
}
/verify/mtn?phone_number=0241234567&consent_type=sms| Field | Type | Required | Description |
|---|---|---|---|
| phone_number | string | Yes | The MTN number. |
| consent_type | string | No | none, sms or ussd. Defaults to none. |
Verification errors
A failed verification returns status: "error", a machine-readable code, and, when an attempt was recorded, its reference.
{
"status": "error",
"message": "Ghana Card details do not match.",
"code": "VERIFICATION_FAILED",
"reference": "VERIFY-3F2A9C1E-..."
}
| HTTP | code | Meaning | Charged? |
|---|---|---|---|
| 422 | VERIFICATION_FAILED | The lookup ran but the details did not check out or nothing was found. | No (refunded) |
| 422 | (validation) | A field is missing or malformed. The response lists each field. | No |
| 402 | INSUFFICIENT_BALANCE | Your wallet cannot cover the lookup. | No |
| 502 | GATEWAY_UNAVAILABLE | The verification service could not be reached. Retry shortly. | No (refunded) |
| 503 | SERVICE_UNAVAILABLE | This service is not enabled for sale right now. | No |
| 503 | GATEWAY_NOT_CONFIGURED | Verification is being set up. Contact Qwiktop. | No |
8. Testing and sandbox
Try every endpoint without spending real money or contacting a real provider. Create a Test key on the Developer page and use it exactly like a live key. Nothing you do with a test key touches your real wallet, real orders or real verification records.
| Live key | Test key | |
|---|---|---|
| Key looks like | qt-… | qt-test-… |
| Wallet | Your real Qwiktop wallet | A virtual wallet, topped up on the Developer page |
| Catalogue and prices | Live | The same live plans and prices |
| Orders and lookups | Real | Simulated. References start with TEST- |
| Response header | None added | X-Qwiktop-Mode: sandbox |
How a simulated order behaves
- The virtual wallet is charged at once, exactly as a live order would be, and a low balance returns 402.
- The order starts pending and settles to success or failed after a few seconds. Check it with GET /transactions/{reference}.
- A failed order refunds the virtual wallet.
- If your key has a webhook URL, a signed transaction.updated event is sent when the order settles. It has the same body as a live event plus "test": true:
{
"event": "transaction.updated",
"timestamp": "2026-09-25T12:00:00+00:00",
"data": {
"reference": "TEST-ABC123XYZ456",
"status": "success",
"service": "MTN_AIRTIME",
"amount": 10.0,
"test": true
}
}
Wallet funding in the sandbox has no checkout: POST /wallet/fund credits the virtual wallet straight away and returns authorization_url: null. There is no two-minute cooldown between orders to the same number, so you can repeat tests quickly.
Make an order fail on purpose
End the phone number (data, airtime) or the account (bills) with one of these codes:
| Ends with | What happens |
|---|---|
| 0001 | The order fails and your virtual wallet is refunded. |
| 0002 | The order stays pending and never settles. |
| 0003 | The order is refused straight away with a 503 error. |
Make a verification fail on purpose
End the phone number, account number or voter ID with one of these codes. For a Ghana Card the nine-digit number in the middle decides, for example GHA-000000001-0.
| Ends with | What happens |
|---|---|
| 0001 | The lookup fails with VERIFICATION_FAILED and you are not charged. |
| 0002 | The lookup fails with GATEWAY_UNAVAILABLE and you are not charged. |
Every other number succeeds with a clearly fake result. The same input always gives the same result. Successful lookups are charged to the virtual wallet at the real price, and the response has meta.sandbox: true.
Reset and limits
Use Reset sandbox on your test key to delete its test orders and restore the starting virtual balance. Test requests count towards your key's daily limit and respect its IP whitelist. Your account can hold a limited number of test keys. Test keys can never be turned into live keys.
9. Webhooks
If you save a webhook URL, Qwiktop sends event notifications when transaction state changes.
{
"event": "transaction.updated",
"timestamp": "2026-06-27T10:30:00+00:00",
"data": {
"reference": "QT-...",
"status": "success",
"service": "MTN_DATA"
}
}
Signature verification
Every webhook contains X-Qwiktop-Signature. Compute HMAC-SHA256 over the JSON payload using your webhook secret and compare it to the header.
$expected = hash_hmac('sha256', $rawJsonBody, $webhookSecret);
if (! hash_equals($expected, $_SERVER['HTTP_X_QWIKTOP_SIGNATURE'])) {
http_response_code(401);
exit;
}
10. Error handling
The request was accepted or created.
Missing, invalid, or paused API key.
Wallet balance cannot cover the requested order.
Required fields are missing or invalid.
Daily API limit has been reached.
External payment/provider system is temporarily unavailable.
Retry rules
| Case | What to do |
|---|---|
| Network timeout | Check transaction status before retrying. Avoid duplicate customer charges. |
| 402 balance | Fund wallet first, then retry. |
| 422 validation | Fix request body. Do not retry unchanged. |
| 503 provider issue | Retry later with backoff, or allow Qwiktop queueing to finish. |
11. Security checklist
- Store API keys only on your server.
- Never expose keys inside frontend JavaScript.
- Use HTTPS for all production calls and webhook URLs.
- Regenerate a key immediately if it is leaked.
- Return HTTP 200 quickly from your webhook endpoint after receiving a valid event.
- Use transaction references to make your own system idempotent.
12. Full cURL examples
Buy data
curl -X POST "https://qwiktop.com/api/v1/buy-data" \
-H "X-API-Key: qt_your_api_key" \
-H "Content-Type: application/json" \
-d '{"network":"mtn","plan_id":1,"phone":"0241234567"}'
Buy airtime
curl -X POST "https://qwiktop.com/api/v1/buy-airtime" \
-H "X-API-Key: qt_your_api_key" \
-H "Content-Type: application/json" \
-d '{"network":"telecel","amount":10,"phone":"0500000000"}'
Pay bill
curl -X POST "https://qwiktop.com/api/v1/pay-bill" \
-H "X-API-Key: qt_your_api_key" \
-H "Content-Type: application/json" \
-d '{"type":"electricity","provider":"ecg","account":"12345678901","amount":20}'
Verify a Ghana Card
curl -X POST "https://qwiktop.com/api/v1/verify/ghana-card" \
-H "X-API-Key: qt_your_api_key" \
-H "Content-Type: application/json" \
-d '{"card_number":"GHA-123456789-0","surname":"Mensah","first_names":"Ama Serwaa","gender":"Female","date_of_birth":"1990-01-31"}'
Look up the name on a phone number
curl "https://qwiktop.com/api/v1/verify/msisdn?phone_number=0241234567" \ -H "X-API-Key: qt_your_api_key"