must have this registration link
https:// mybravepay.com/brave-vtu-otp/
also check OTP Verification form and change the link to your new site link
must have
https:// mybravepay.com/wallet-funding-success/
Check and fix what’s the problem that amount funded by user is not reflected on user wallet balance after user has filled and submitted funding form and made Payment using paystack, amount deducted from user bank account but amount not being added to user wallet.
First generate me a file code name user-wallet-balance.php and the file code to enable user to view wallet balance also generate me the file short code to enable me to place it in a page
User should select (data, airtime, electricity etc)
System check user wallet balance
If balance is insufficient the system should stop transaction
System sends purchase request to API.
If API return success,
deduct user wallet log transactions
Show success message
If API returns failed, do not deduct wallet, show contact support for assistant
ADD
pending
processing
successful
failed
refunded
FINAL FLOW
User
↓
Select Service
↓
Validate Inputs
↓
Check Wallet Balance
↓
Generate Request ID
↓
Create PENDING Transaction
↓
Reserve Wallet Amount
↓
Call VTU.ng API
↓
completed-api ─────► Deduct Permanently ► SUCCESS
processing-api ────► Wait for Webhook/Requery
failed/refunded ───► Release Reserved Amount ► FAILED
↓
Notify User
BETTER FLOW
User selects service
↓
Validate inputs
↓
Check wallet balance
↓
Generate request_id
↓
Create transaction record (PENDING)
↓
Temporarily reserve/lock wallet amount
↓
Send request to VTU.ng
↓
If completed-api:
Deduct wallet permanently
Mark SUCCESS
Notify user
If processing-api:
Keep transaction PENDING
Wait for webhook/requery
Finalize later
If refunded/failed:
Release reserved amount
Mark FAILED
Notify user
FINAL FLOW
User
↓
Select Service
↓
Validate Inputs
↓
Check Wallet Balance
↓
Generate Request ID
↓
Create PENDING Transaction
↓
Reserve Wallet Amount
↓
Call VTU.ng API
↓
completed-api ─────► Deduct Permanently ► SUCCESS
processing-api ────► Wait for Webhook/Requery
failed/refunded ───► Release Reserved Amount ► FAILED
↓
Notify User
User
↓
Select Network + Phone + Amount
↓
Enter Transaction PIN
↓
Validate Input
↓
Check Wallet Balance
↓
Create Transaction Record (Pending)
↓
Generate Reference ID
↓
Call VTU.ng API
↓
If Success
↓
Deduct Wallet
↓
Update Transaction = Success
↓
Log Wallet Debit
↓
Notify User
Else
↓
Update Transaction = Failed
↓
Do NOT Deduct Wallet
↓
Notify User
paystack after payment
https:// mybravepay.com/create-account/
https:// mybravepay.com/login/
https:// mybravepay.com/create-account/
https:// mybravepay.com/f-a-q/
https:// mybravepay.com/login/
https: //mybravepay.com/support/
https:// mybravepay.com/call/
https:// mybravepay.com/my-account/
Payload:
The webhook sends a JSON payload with order details, signed with an HMAC-SHA256 signature using the user’s PIN.
Sample Payload:
{ “order_id”: 12345, “status”: “completed-api”, “product_name”: “Airtime”, “quantity”: 1, “amount”: “100.00”, “amount_charged”: “97.50”, “date_created”: “2025-04-12 10:00:00”, “date_updated”: “2025-04-12 10:01:00”, “request_id”: “req_123456789”, “meta_data”:{“network”:”MTN”,”phone”:”08106223552″}, “timestamp”: 1744636800 }
Headers:
Content-Type: application/json
X-Signature: HMAC-SHA256 signature of the payload.
Signature Verification:
Compute the HMAC-SHA256 of the payload using your user_pin and compare it with the X-Signature header.
Sample Verification (Python):
import hmac import hashlib import json def verify_webhook(payload, signature, user_pin): computed_signature = hmac.new( user_pin.encode(‘utf-8’), json.dumps(payload, separators=(‘,’, ‘:’)).encode(‘utf-8’), hashlib.sha256 ).hexdigest() return hmac.compare_digest(computed_signature, signature) # Example payload = {…} # Webhook payload signature = “received_x_signature” user_pin = “your_user_pin” is_valid = verify_webhook(payload, signature, user_pin) print(“Signature valid:”, is_valid)Collapse
Sample Verification (PHP):
<?php // User PIN from your VTU.ng account (stored securely, e.g., in environment variables or config) $user_pin = ‘your_user_pin’; // Replace with your actual user PIN // Get the raw POST body $payload = file_get_contents(‘php://input’); // Decode the JSON payload to access its data (optional, for processing) $payload_data = json_decode($payload, true); // Get the X-Signature header $received_signature = isset($_SERVER[‘HTTP_X_SIGNATURE’]) ? $_SERVER[‘HTTP_X_SIGNATURE’] : ”; // Compute the HMAC-SHA256 signature // The payload must be used as-is (raw JSON string, no extra whitespace) $computed_signature = hash_hmac(‘sha256’, $payload, $user_pin); // Compare signatures securely if (hash_equals($computed_signature, $received_signature)) { // Signature is valid, process the webhook http_response_code(200); // Send 200 OK to acknowledge receipt // Example: Log the payload or update order status file_put_contents(‘webhook_log.txt’, print_r($payload_data, true), FILE_APPEND); // Example processing if (isset($payload_data[‘order_id’]) && isset($payload_data[‘status’])) { $order_id = $payload_data[‘order_id’]; $status = $payload_data[‘status’]; // Update your database or system based on status if ($status === ‘completed-api’) { // Handle completed order (e.g., mark as fulfilled) error_log(“Order $order_id completed successfully”); } elseif ($status === ‘refunded’) { // Handle refunded order (e.g., issue refund notification) error_log(“Order $order_id was refunded”); } } // Output a response (optional, VTU.ng expects 200 status) echo json_encode([‘status’ => ‘success’, ‘message’ => ‘Webhook processed’]); } else { // Invalid signature http_response_code(403); // Forbidden echo json_encode([‘status’ => ‘error’, ‘message’ => ‘Invalid signature’]); } ?>Collapse
Response:
Expects a 200 OK response.
Security:
Use HTTPS for webhook URLs.
Validate X-Signature to ensure authenticity.
Store user_pin securely.
Error Handling:
Invalid or missing webhook URLs prevent notifications.
Ensure endpoint availability to avoid missed notifications.
Error Codes
The API returns standard HTTP status codes and custom error codes:
HTTP Status
Error Code
Description
400
missing_fields
Required parameters missing.
400
missing_request_id
Request ID not provided (/api/v2/requery).
400
invalid_field
Invalid input (e.g., service_id, phone).
400
invalid_service
Invalid service provider.
400
invalid_service_id
Invalid service ID (/api/v2/variations/*).
400
invalid_variation_id
Invalid variation ID.
400
invalid_request_id
Request ID exceeds 50 characters.
400
below_minimum_amount
Amount below minimum.
400
above_maximum_amount
Amount above maximum.
400
below_customer_arrears
Amount below arrears (electricity).
400
invalid_product
Product ID invalid or unavailable.
400
product_unavailable
Product out of stock.
400
order_failed
Failed to create order.
400
request_id_error
Failed to save request ID.
400
failure
Verification failed (/api/v2/verify-customer).
402
insufficient_funds
Insufficient wallet balance.
403
jwt_auth_failed
Invalid Credentials.
403
jwt_auth_invalid_token
Signature verification failed.
403
rest_forbidden
Unauthorized access (invalid token, IP not whitelisted).
404
rest_no_route
No route was found matching the URL and request method.
404
no_product
Product ID invalid or not variable (/api/v2/variations/*).
404
order_not_found
No order found for request ID (/api/v2/requery).
409
duplicate_request
Request already processing.
409
duplicate_request_id
Request ID already exists.
409
duplicate_order
Duplicate order within 3 minutes.
429
rate_limit_exceeded
Too many requests.
429
wallet_busy
Wallet transaction in progress.
500
wallet_error
Unable to retrieve wallet balance.
Integration Guidelines
Best Practices
1. Unique Request IDs:
Generate unique request_id values (e.g., req_20250412123456_abc).
Store request_id for requery purposes.
2. Error Handling:
Handle HTTP status codes and error messages.
Retry transient errors (e.g., wallet_busy) with exponential backoff.
3. Rate Limiting:
Respect the 3-minute lockout for duplicate orders.
Cache variation data locally to reduce API calls.
4. KYC Compliance:
Complete KYC for higher daily limits (Tier 1: ₦50,000; Tier 2: ₦500,000; Tier 3: Unlimited).
5. IP Whitelisting:
Whitelist server IPs on the developer tab of the account settings page.
6. Variations:
Fetch valid variation_id values using /api/v2/variations/data and /api/v2/variations/tv before purchasing.
7. Customer Verification:
Use /api/v2/verify-customer before electricity, betting, or TV purchases to validate customer IDs.
8. Webhooks:
Configure secure HTTPS webhook endpoints.
Verify signatures using user_pin.
Handle duplicates by checking order_id and timestamp.
9. Order Requery:
Use /api/v2/requery for status checks if webhooks fail or are not enough.
Map request_id to orders for correlation.
Sample Integration Codes
Python (Using requests)
import requests import hmac import hashlib import json import os # Base URLs AUTH_URL = “https://vtu.ng/wp-json/jwt-auth/v1/token” API_URL = “https://vtu.ng/wp-json/api/v2/” # Credentials (store securely in production) USERNAME = os.getenv(“VTU_USERNAME”, “your_vtu_username”) PASSWORD = os.getenv(“VTU_PASSWORD”, “your_vtu_password”) USER_PIN = os.getenv(“VTU_USER_PIN”, “your_user_pin”) # For webhook verification # Global token TOKEN = None def get_access_token(): “””Obtain a JWT token for authentication.””” global TOKEN payload = {“username”: USERNAME, “password”: PASSWORD} headers = {“Content-Type”: “application/json”} try: response = requests.post(AUTH_URL, json=payload, headers=headers) response.raise_for_status() data = response.json() if “token” in data: TOKEN = data[“token”] return TOKEN else: raise Exception(data.get(“message”, “Authentication failed”)) except requests.exceptions.HTTPError as http_err: status = response.status_code if status == 401: raise Exception(“Invalid credentials”) elif status == 403: raise Exception(“IP not whitelisted”) elif status == 400: raise Exception(“Invalid request”) raise Exception(f”HTTP error: {http_err}”) except requests.exceptions.RequestException as err: raise Exception(f”Request error: {err}”) def get_headers(): “””Return headers with Bearer token.””” if not TOKEN: get_access_token() return { “Authorization”: f”Bearer {TOKEN}”, “Content-Type”: “application/json” } def check_balance(): “””Check wallet balance.””” try: response = requests.get(f”{API_URL}balance”, headers=get_headers()) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error checking balance: {err}”) def purchase_airtime(): “””Purchase airtime.””” payload = { “request_id”: “req_20250412123456_airtime”, “phone”: “08012345678”, “service_id”: “mtn”, “amount”: 100 } try: response = requests.post(f”{API_URL}airtime”, json=payload, headers=get_headers()) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error purchasing airtime: {err}”) def get_data_variations(service_id=None): “””Get data plan variations.””” url = f”{API_URL}variations/data” if service_id: url += f”?service_id={service_id}” try: response = requests.get(url) # Public endpoint, no auth response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error getting data variations: {err}”) def purchase_data(): “””Purchase a data plan.””” payload = { “request_id”: “req_20250412123456_data”, “phone”: “08012345678”, “service_id”: “mtn”, “variation_id”: “2682” } try: response = requests.post(f”{API_URL}data”, json=payload, headers=get_headers()) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error purchasing data: {err}”) def verify_customer(service_id, customer_id, variation_id=None): “””Verify customer details.””” payload = {“customer_id”: customer_id, “service_id”: service_id} if variation_id: payload[“variation_id”] = variation_id try: response = requests.post(f”{API_URL}verify-customer”, json=payload, headers=get_headers()) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error verifying customer: {err}”) def purchase_electricity(): “””Purchase electricity units.””” payload = { “request_id”: “req_20250412123456_electricity”, “customer_id”: “12345678901”, “service_id”: “ikeja-electric”, “variation_id”: “prepaid”, “amount”: 1000 } try: response = requests.post(f”{API_URL}electricity”, json=payload, headers=get_headers()) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error purchasing electricity: {err}”) def fund_betting_account(): “””Fund a betting account.””” payload = { “request_id”: “req_20250412123456_betting”, “customer_id”: “USER12345”, “service_id”: “Bet9ja”, “amount”: 500 } try: response = requests.post(f”{API_URL}betting”, json=payload, headers=get_headers()) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error funding betting account: {err}”) def get_tv_variations(service_id=None): “””Get TV package variations.””” url = f”{API_URL}variations/tv” if service_id: url += f”?service_id={service_id}” try: response = requests.get(url) # Public endpoint, no auth response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error getting TV variations: {err}”) def purchase_tv_subscription(): “””Purchase a cable TV subscription.””” payload = { “request_id”: “req_20250412123456_tv”, “customer_id”: “1234567890”, “service_id”: “dstv”, “variation_id”: “3713” } try: response = requests.post(f”{API_URL}tv”, json=payload, headers=get_headers()) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error purchasing TV subscription: {err}”) def purchase_epins(): “””Purchase ePINs.””” payload = { “request_id”: “req_20250412123456_epins”, “service_id”: “mtn”, “value”: 500, “quantity”: 1 } try: response = requests.post(f”{API_URL}epins”, json=payload, headers=get_headers()) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error purchasing ePINs: {err}”) def requery_order(request_id): “””Requery an order status.””” payload = {“request_id”: request_id} try: response = requests.post(f”{API_URL}requery”, json=payload, headers=get_headers()) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as err: raise Exception(f”Error requerying order: {err}”) def verify_webhook(payload, signature): “””Verify webhook signature (HMAC-SHA256).””” computed_signature = hmac.new( USER_PIN.encode(‘utf-8’), payload.encode(‘utf-8’), hashlib.sha256 ).hexdigest() return hmac.compare_digest(computed_signature, signature) # Example Webhook Handler (using Flask) from flask import Flask, request app = Flask(__name__) @app.route(‘/webhook’, methods=[‘POST’]) def webhook(): payload = request.get_data(as_text=True) signature = request.headers.get(‘X-Signature’, ”) if verify_webhook(payload, signature): data = request.get_json() # Process webhook (e.g., update order status) print(“Webhook verified:”, data) return {“status”: “success”}, 200 else: print(“Invalid signature”) return {“status”: “error”, “message”: “Invalid signature”}, 403 # Example Usage if __name__ == “__main__”: try: print(“Token:”, get_access_token()) print(“Balance:”, check_balance()) print(“Airtime:”, purchase_airtime()) print(“Data Variations:”, get_data_variations(“mtn”)) print(“Data Purchase:”, purchase_data()) print(“Verify Customer:”, verify_customer(“ikeja-electric”, “12345678901”, “prepaid”)) print(“Electricity:”, purchase_electricity()) print(“Betting:”, fund_betting_account()) print(“TV Variations:”, get_tv_variations(“dstv”)) print(“TV Subscription:”, purchase_tv_subscription()) print(“ePINs:”, purchase_epins()) print(“Requery:”, requery_order(“req_20250412123456_data”)) except Exception as e: print(f”Error: {e}”) # Run Flask app for webhook testing # app.run(port=5000)Collapse
PHP (Using cURL)
<?php // Base URLs define(‘AUTH_URL’, ‘https://vtu.ng/wp-json/jwt-auth/v1/token’); define(‘API_URL’, ‘https://vtu.ng/wp-json/api/v2/’); // Credentials (store securely in production) $username = getenv(‘VTU_USERNAME’) ?: ‘your_vtu_username’; $password = getenv(‘VTU_PASSWORD’) ?: ‘your_vtu_password’; $user_pin = getenv(‘VTU_USER_PIN’) ?: ‘your_vtu_pin’; $token = null; function get_access_token() { global $username, $password, $token; $payload = json_encode([‘username’ => $username, ‘password’ => $password]); $ch = curl_init(AUTH_URL); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, [‘Content-Type: application/json’]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } $data = json_decode($response, true); curl_close($ch); if ($http_code === 200 && isset($data[‘token’])) { $token = $data[‘token’]; return $token; } $message = $data[‘message’] ?? ‘Unknown error’; if ($http_code === 400) { throw new Exception(“Invalid request: $message”); } elseif ($http_code === 401) { throw new Exception(“Invalid credentials: $message”); } elseif ($http_code === 403) { throw new Exception(“IP not whitelisted: $message”); } throw new Exception(“Error ($http_code): $message”); } function get_headers() { global $token; if (!$token) { get_access_token(); } return [ “Authorization: Bearer $token”, “Content-Type: application/json” ]; } function check_balance() { $ch = curl_init(API_URL . ‘balance’); curl_setopt($ch, CURLOPT_HTTPHEADER, get_headers()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } function purchase_airtime() { $payload = json_encode([ ‘request_id’ => ‘req_20250412123456_airtime’, ‘phone’ => ‘08012345678’, ‘service_id’ => ‘mtn’, ‘amount’ => 100 ]); $ch = curl_init(API_URL . ‘airtime’); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, get_headers()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } function get_data_variations($service_id = null) { $url = API_URL . ‘variations/data’; if ($service_id) { $url .= ‘?service_id=’ . urlencode($service_id); } $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } function purchase_data() { $payload = json_encode([ ‘request_id’ => ‘req_20250412123456_data’, ‘phone’ => ‘08012345678’, ‘service_id’ => ‘mtn’, ‘variation_id’ => ‘2682’ ]); $ch = curl_init(API_URL . ‘data’); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, get_headers()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } function verify_customer($service_id, $customer_id, $variation_id = null) { $payload = [‘customer_id’ => $customer_id, ‘service_id’ => $service_id]; if ($variation_id) { $payload[‘variation_id’] = $variation_id; } $payload = json_encode($payload); $ch = curl_init(API_URL . ‘verify-customer’); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, get_headers()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } function purchase_electricity() { $payload = json_encode([ ‘request_id’ => ‘req_20250412123456_electricity’, ‘customer_id’ => ‘12345678901’, ‘service_id’ => ‘ikeja-electric’, ‘variation_id’ => ‘prepaid’, ‘amount’ => 1000 ]); $ch = curl_init(API_URL . ‘electricity’); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, get_headers()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } function fund_betting_account() { $payload = json_encode([ ‘request_id’ => ‘req_20250412123456_betting’, ‘customer_id’ => ‘USER12345’, ‘service_id’ => ‘Bet9ja’, ‘amount’ => 500 ]); $ch = curl_init(API_URL . ‘betting’); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, get_headers()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } function get_tv_variations($service_id = null) { $url = API_URL . ‘variations/tv’; if ($service_id) { $url .= ‘?service_id=’ . urlencode($service_id); } $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } function purchase_tv_subscription() { $payload = json_encode([ ‘request_id’ => ‘req_20250412123456_tv’, ‘customer_id’ => ‘1234567890’, ‘service_id’ => ‘dstv’, ‘variation_id’ => ‘3713’ ]); $ch = curl_init(API_URL . ‘tv’); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, get_headers()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } function purchase_epins() { $payload = json_encode([ ‘request_id’ => ‘req_20250412123456_epins’, ‘service_id’ => ‘mtn’, ‘value’ => 500, ‘quantity’ => 1 ]); $ch = curl_init(API_URL . ‘epins’); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, get_headers()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } function requery_order($request_id) { $payload = json_encode([‘request_id’ => $request_id]); $ch = curl_init(API_URL . ‘requery’); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_HTTPHEADER, get_headers()); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if ($response === false) { throw new Exception(‘cURL Error: ‘ . curl_error($ch)); } curl_close($ch); return json_decode($response, true); } // Webhook Verification Handler if ($_SERVER[‘REQUEST_METHOD’] === ‘POST’ && isset($_SERVER[‘HTTP_X_SIGNATURE’])) { try { $payload = file_get_contents(‘php://input’); $received_signature = $_SERVER[‘HTTP_X_SIGNATURE’] ?? ”; $computed_signature = hash_hmac(‘sha256’, $payload, $user_pin); if (hash_equals($computed_signature, $received_signature)) { http_response_code(200); $data = json_decode($payload, true); // Process webhook (e.g., update order status) file_put_contents(‘webhook_log.txt’, print_r($data, true), FILE_APPEND); if (isset($data[‘order_id’]) && isset($data[‘status’])) { $order_id = $data[‘order_id’]; $status = $data[‘status’]; error_log(“Order $order_id: $status”); } echo json_encode([‘status’ => ‘success’, ‘message’ => ‘Webhook processed’]); } else { http_response_code(403); echo json_encode([‘status’ => ‘error’, ‘message’ => ‘Invalid signature’]); } } catch (Exception $e) { http_response_code(500); echo json_encode([‘status’ => ‘error’, ‘message’ => $e->getMessage()]); } exit; } // Example Usage try { echo “Token: ” . get_access_token() . “\n”; echo “Balance: ” . print_r(check_balance(), true) . “\n”; echo “Airtime: ” . print_r(purchase_airtime(), true) . “\n”; echo “Data Variations: ” . print_r(get_data_variations(‘mtn’), true) . “\n”; echo “Data Purchase: ” . print_r(purchase_data(), true) . “\n”; echo “Verify Customer: ” . print_r(verify_customer(‘ikeja-electric’, ‘12345678901’, ‘prepaid’), true) . “\n”; echo “Electricity: ” . print_r(purchase_electricity(), true) . “\n”; echo “Betting: ” . print_r(fund_betting_account(), true) . “\n”; echo “TV Variations: ” . print_r(get_tv_variations(‘dstv’), true) . “\n”; echo “TV Subscription: ” . print_r(purchase_tv_subscription(), true) . “\n”; echo “ePINs: ” . print_r(purchase_epins(), true) . “\n”; echo “Requery: ” . print_r(requery_order(‘req_20250412123456_data’), true) . “\n”; } catch (Exception $e) { echo “Error: ” . $e->getMessage() . “\n”; } ?>Collapse
JavaScript (Using fetch)
// Base URLs const AUTH_URL = “https://vtu.ng/wp-json/jwt-auth/v1/token”; const API_URL = “https://vtu.ng/wp-json/api/v2/”; // Credentials (store securely in production) const USERNAME = process.env.VTU_USERNAME || “your_vtu_username”; const PASSWORD = process.env.VTU_PASSWORD || “your_vtu_password”; const USER_PIN = process.env.VTU_USER_PIN || “your_user_pin”; // For webhook verification let token = null; async function getAccessToken() { const payload = { username: USERNAME, password: PASSWORD }; const response = await fetch(AUTH_URL, { method: “POST”, headers: { “Content-Type”: “application/json” }, body: JSON.stringify(payload) }); if (!response.ok) { if (response.status === 400) throw new Error(“Invalid request”); if (response.status === 401) throw new Error(“Invalid credentials”); if (response.status === 403) throw new Error(“IP not whitelisted”); throw new Error(HTTP error: ${response.status}); } const data = await response.json(); if (data.token) { token = data.token; return token; } throw new Error(data.message || “Authentication failed”); } function getHeaders() { if (!token) throw new Error(“Token not initialized. Call getAccessToken first.”); return { “Authorization”: Bearer ${token}, “Content-Type”: “application/json” }; } async function checkBalance() { const response = await fetch(${API_URL}balance, { method: “GET”, headers: getHeaders() }); if (!response.ok) throw new Error(Error checking balance: ${response.status}); return response.json(); } async function purchaseAirtime() { const payload = { request_id: “req_20250412123456_airtime”, phone: “08012345678”, service_id: “mtn”, amount: 100 }; const response = await fetch(${API_URL}airtime, { method: “POST”, headers: getHeaders(), body: JSON.stringify(payload) }); if (!response.ok) throw new Error(Error purchasing airtime: ${response.status}); return response.json(); } async function getDataVariations(serviceId = null) { let url = ${API_URL}variations/data; if (serviceId) url += ?service_id=${serviceId}; const response = await fetch(url); if (!response.ok) throw new Error(Error getting data variations: ${response.status}); return response.json(); } async function purchaseData() { const payload = { request_id: “req_20250412123456_data”, phone: “08012345678”, service_id: “mtn”, variation_id: “2682” }; const response = await fetch(${API_URL}data, { method: “POST”, headers: getHeaders(), body: JSON.stringify(payload) }); if (!response.ok) throw new Error(Error purchasing data: ${response.status}); return response.json(); } async function verifyCustomer(serviceId, customerId, variationId = null) { const payload = { customer_id: customerId, service_id: serviceId }; if (variationId) payload.variation_id = variationId; const response = await fetch(${API_URL}verify-customer, { method: “POST”, headers: getHeaders(), body: JSON.stringify(payload) }); if (!response.ok) throw new Error(Error verifying customer: ${response.status}); return response.json(); } async function purchaseElectricity() { const payload = { request_id: “req_20250412123456_electricity”, customer_id: “12345678901”, service_id: “ikeja-electric”, variation_id: “prepaid”, amount: 1000 }; const response = await fetch(${API_URL}electricity, { method: “POST”, headers: getHeaders(), body: JSON.stringify(payload) }); if (!response.ok) throw new Error(Error purchasing electricity: ${response.status}); return response.json(); } async function fundBettingAccount() { const payload = { request_id: “req_20250412123456_betting”, customer_id: “USER12345”, service_id: “Bet9ja”, amount: 500 }; const response = await fetch(${API_URL}betting, { method: “POST”, headers: getHeaders(), body: JSON.stringify(payload) }); if (!response.ok) throw new Error(Error funding betting account: ${response.status}); return response.json(); } async function getTvVariations(serviceId = null) { let url = ${API_URL}variations/tv; if (serviceId) url += ?service_id=${serviceId}; const response = await fetch(url); if (!response.ok) throw new Error(Error getting TV variations: ${response.status}); return response.json(); } async function purchaseTvSubscription() { const payload = { request_id: “req_20250412123456_tv”, customer_id: “1234567890”, service_id: “dstv”, variation_id: “3713” }; const response = await fetch(${API_URL}tv, { method: “POST”, headers: getHeaders(), body: JSON.stringify(payload) }); if (!response.ok) throw new Error(Error purchasing TV subscription: ${response.status}); return response.json(); } async function purchaseEpins() { const payload = { request_id: “req_20250412123456_epins”, service_id: “mtn”, value: 500, quantity: 1 }; const response = await fetch(${API_URL}epins, { method: “POST”, headers: getHeaders(), body: JSON.stringify(payload) }); if (!response.ok) throw new Error(Error purchasing ePINs: ${response.status}); return response.json(); } async function requeryOrder(requestId) { const payload = { request_id: requestId }; const response = await fetch(${API_URL}requery, { method: “POST”, headers: getHeaders(), body: JSON.stringify(payload) }); if (!response.ok) throw new Error(Error requerying order: ${response.status}); return response.json(); } // Webhook Verification (Node.js with Express) const express = require(‘express’); const crypto = require(‘crypto’); const app = express(); app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } })); app.post(‘/webhook’, (req, res) => { const signature = req.headers[‘x-signature’] || ”; const payload = req.rawBody.toString(); const computedSignature = crypto .createHmac(‘sha256’, USER_PIN) .update(payload) .digest(‘hex’); if (crypto.timingSafeEqual(Buffer.from(computedSignature), Buffer.from(signature))) { console.log(“Webhook verified:”, req.body); // Process webhook (e.g., update order status) res.status(200).json({ status: “success” }); } else { console.error(“Invalid signature”); res.status(403).json({ status: “error”, message: “Invalid signature” }); } }); // Example Usage (async () => { try { console.log(“Token:”, await getAccessToken()); console.log(“Balance:”, await checkBalance()); console.log(“Airtime:”, await purchaseAirtime()); console.log(“Data Variations:”, await getDataVariations(“mtn”)); console.log(“Data Purchase:”, await purchaseData()); console.log(“Verify Customer:”, await verifyCustomer(“ikeja-electric”, “12345678901”, “prepaid”)); console.log(“Electricity:”, await purchaseElectricity()); console.log(“Betting:”, await fundBettingAccount()); console.log(“TV Variations:”, await getTvVariations(“dstv”)); console.log(“TV Subscription:”, await purchaseTvSubscription()); console.log(“ePINs:”, await purchaseEpins()); console.log(“Requery:”, await requeryOrder(“req_20250412123456_data”)); } catch (error) { console.error(“Error:”, error.message); } })(); // Start server for webhook testing // app.listen(3000, () => console.log(“Webhook server running on port 3000”));Collapse
Additional Notes
1. Transaction Status:
Orders transition through: initiated-api, processing-api, completed-api, refunded, etc.
Use /api/v2/requery or webhooks for status updates.
2. Webhooks:
Configure the URL in the VTU.ng developer settings dashboard for completed-api and refunded notifications.
Verify signatures and handle duplicates.
3. Variations:
Use /api/v2/variations/data and /api/v2/variations/tv to fetch valid variation_id values.
Public access simplifies pre-purchase validation.
4. Customer Verification:
Mandatory before electricity, betting, or TV purchases to ensure valid customer IDs.
5. Rate Limits:
3-minute lockout for duplicate orders.
6. Support:
Contact support@vtu.ng or use the support desk.
7. Testing:
Test small transactions and webhooks before production.
Changelog
Version 2.0 (July 23, 2025):
Added /variations/data, /variations/tv, /requery, and webhook notifications.
Syncronised processing of frontend orders and API orders.
Changed the authentication system.
Other maintenance fixes.
Version 1.0 (August 1st, 2020):
Initial release with core transactional endpoints.
CONTACT US
**Address:**1 Austin Ndigwe Avenue, Aguaba, Awka, Anambra, Nigeria
Phone (WhatsApp):+2347045461790
**Email:**support@vtu.ng
**Office Days/Hours:**Mon – Sat / 9:00 AM – 5:00 PM
ABOUT US
VTU.ng is a virtual top-up platform owned and operated by FraNKAPPWeb Technologies (BN 2384195). We are duly registered with the corporate affairs commission (CAC).
Our services include but are not limited to Buying/Reselling of Cheap Data & Airtime, Electricity Bills Payment, Cable TV Subscription and Airtime to Cash Conversion.
Enjoy our massive discounts for resellers and partners and make money with us. Learn more
RESOURCES
Airtime
Data
Cable TV
Electricity
API Documentation
Track Order
Pricing
About
Blog
Support Desk
FOLLOW US
Facebook Twitter Instagram Tiktok
VTU.ng Mobile App
VTU.ngCopyright © 2026 VTU.ng. All Rights Reserved. Terms & Conditions | Privacy Policy
Check to confirm is we can use this information to integrate vtu.ng vtu service to our site www.mybravepay.com
support@vtu.ng
Call/WhatsApp
07045461790
**
VTU.ng
**
VTU API
VTU API for Airtime, Data, Cable TV Subscription, Electricity Bills Payment, Bet Accounts Funding & Recharge Cards Printing in Nigeria (MTN, Glo, Airtel, T2mobile, PHED, DStv, GOtv, Startimes, SportyBet)
Table of Contents
Overview
Base URL
Authentication
Authentication Requirements
Obtaining an Access Token
Endpoints
Check Wallet Balance
Purchase Airtime
Get Data Variations
Purchase Data
Verify Customer
Purchase Electricity
Fund Betting Account
Get Cable TV Variations
Purchase Cable TV Subscription
Purchase ePINs
Requery Order
Webhook Notifications
Error Codes
Integration Guidelines
Best Practices
Sample Integration Codes
Additional Notes
Changelog
Overview
The VTU API serves as a robust RESTful interface that simplifies integration for buying airtime, data plans, electricity bill payments, cable TV subscriptions, funding bet accounts, and printing ePINs for recharge cards. It includes handy endpoints to view wallet balances, list service variations, verify customer info, check on orders, and get webhook alerts about status changes.
Developers can tap into this to weave all the virtual top-up and bill payment features from VTU.ng right into their own web, desktop, or mobile applications. Start your VTU venture by hooking up to our API and reselling services throughout Nigeria and Africa.
As a user of the API, you get great rates: 1GB data for ₦499, 3% off airtime, 1.5% savings on cable TV, another 1.5% on electricity bills, 4% on ePINs, 0.2% on betting funds, plus zero service charges. Check the pricing page for full breakdowns.
Best VTU API Provider in Nigeria
We rely on a solid setup for SIM hosting. Our SIM cards run on in-house servers, handling VTU tasks in mere nanoseconds to deliver instantly. They trigger USSD commands and dispatch SMS when necessary. Usually, we link straight to telecom APIs without middlemen. Cable TV and electricity services connect directly to provider systems via premium links, and we leverage bank APIs as needed. All this keeps things affordable and dependable.
This guide walks you through authentication, sending requests, dealing with responses, and setting up the VTU API, including code examples in multiple languages.
Any orders that don’t go through get refunded automatically.
This is the latest VTU API v2, which is more Powerful and Robust.
See Legacy API v1
Base URL
The base URL for all API requests is:
https://vtu.ng/wp-json
Authentication
The VTU.ng API requires authentication for most endpoints to ensure secure access. Users must have a VTU.ng account with the Reseller role. Some endpoints, such as service variations, are publicly accessible without authentication.
Authentication Requirements
1. VTU.ng User Account:
A valid VTU.ng user account with the reseller role is required for transactional endpoints.
The account must complete KYC (Know Your Customer) verification for higher transaction limits:
Tier 1: Email verified (up to ₦50,000 daily limit).
Tier 2: BVN verified (up to ₦500,000 daily limit).
Tier 3: Face, ID and address verified (unlimited).
2. IP Whitelisting (Optional):
Your server’s IP address can be added to the whitelist in the developer tab of your account settings page for authenticated endpoints. This process is optional if you want to allow API requests from any IP. If you whitelist IPs, only the requests from the whitelisted IPs will be allowed.
Obtaining an Access Token
Endpoint: POST /jwt-auth/v1/token
Description: Authenticates a user and returns a JSON Web Token (JWT) for accessing protected VTU.ng API endpoints.
Authentication: Required (Email or Username, Password).
Parameters:
Parameter
Type
Required
Description
username
String
Yes
The user’s VTU.ng account email or username.
password
String
Yes
The user’s VTU.ng account password.
Sample Login Request:
curl -X POST https://vtu.ng/wp-json/jwt-auth/v1/token \ -H “Content-Type: application/json” \ -d ‘{ “username”: “your_vtu_email_or_username”, “password”: “your_vtu_password” }’
Sample Error Response:
{“code”:”[jwt_auth] incorrect_password”,”message”:”ERROR: The username or password you entered is incorrect. Lost your password?”,”data”:{“status”:403}}
Sample Success Response:
{“token”:”your_jwt_token”,”user_email”:”your_name@email.com”,”user_nicename”:”your_name”,”user_display_name”:”your_name”}
Use the token in the Authorization header for all subsequent requests:
Authorization: Bearer your_jwt_token
Important Notes:
The token expires after 7 days. Obtain a new token regularly (before every request or at least once or twice a week) to ensure smooth operation.
For security, only the latest token remains active. Generating a new token invalidates older ones.
The /api/v2/variations/data and /api/v2/variations/tv endpoints do not require authentication.
Endpoints
The endpoints are organised to reflect the integration flow: checking balances, purchasing airtime, retrieving data variations before purchasing data, verifying customers before purchasing electricity, betting, or TV subscriptions, retrieving TV variations before purchasing TV subscriptions, purchasing ePINs, and requerying orders.
1. Check Wallet Balance
Endpoint: GET /api/v2/balance
Description: Retrieves the current wallet balance of the authenticated user.
Authentication: Required (Bearer token).
Parameters: None
Sample Request:
curl -X GET https://vtu.ng/wp-json/api/v2/balance \ -H “Authorization: Bearer your_jwt_token”
Sample Success Response:
{ “code”: “success”, “message”: “Wallet balance retrieved successfully!”, “data”: { “balance”: 5000.00, “currency”: “NGN” } }
Error Responses:
500: wallet_error – Unable to retrieve wallet balance.
403: rest_forbidden – Unauthorized access (invalid token or IP not whitelisted).
Sample Error Response:
{ “code”: “wallet_error”, “message”: “Unable to retrieve wallet balance. Please try again.”, “data”: { “status”: 500} }
2. Purchase Airtime (VTU): Airtime Recharge API (MTN, Glo, Airtel & T2mobile)
Endpoint: POST /api/v2/airtime
Description: Purchases airtime for a specified phone number and network provider.
Authentication: Required (Bearer token).
Parameters:
Parameter
Type
Required
Description
request_id
String
Yes
Unique identifier (max 50 chars).
phone
String
Yes
Phone number (e.g., 07045461790 or +2347045461790).
service_id
String
Yes
Network provider (mtn, airtel, glo, 9mobile).
amount
Integer
Yes
Airtime amount in NGN (min/max varies).
Validation:
Phone number: 11-16 digits, supports +234 format.
Amount: Min ₦10 (MTN), ₦50 (others); max ₦50,000.
Service ID must match the phone number’s network prefix.
Sample Request:
curl -X POST https://vtu.ng/wp-json/api/v2/airtime \ -H “Authorization: Bearer your_jwt_token” \ -H “Content-Type: application/json” \ -d ‘{ “request_id”: “req_123456789”, “phone”: “07045461790”, “service_id”: “mtn”, “amount”: 100 }’
Sample Processing Order Response:
{ “code”: “success”, “message”: “ORDER PROCESSING”, “data”: { “order_id”: 12345, “status”: “processing-api”, “product_name”: “Airtime”, “service_name”: “MTN”, “phone”: “07045461790”, “amount”: 100, “discount”: “2.50”, “amount_charged”: “97.50”, “initial_balance”: “5000.00”, “final_balance”: “4902.50”, “request_id”: “req_123456789” } }
Sample Completed Order Response:
{ “code”: “success”, “message”: “ORDER COMPLETED”, “data”: { “order_id”: 12345, “status”: “completed-api”, “product_name”: “Airtime”, “service_name”: “MTN”, “phone”: “07045461790”, “amount”: 100, “discount”: “2.50”, “amount_charged”: “97.50”, “initial_balance”: “5000.00”, “final_balance”: “4902.50”, “request_id”: “req_123456789” } }
Sample Refunded Order Response:
{ “code”: “success”, “message”: “ORDER REFUNDED”, “data”: { “order_id”: 12345, “status”: “refunded”, “product_name”: “Airtime”, “service_name”: “MTN”, “phone”: “07045461790”, “amount”: 100, “discount”: “0.00”, “amount_charged”: “0.00”, “initial_balance”: “5000.00”, “final_balance”: “5000.00”, “request_id”: “req_123456789” } }
Error Responses:
400: missing_fields – Required parameters missing.
400: invalid_service – Invalid service ID or phone number.
400: below_minimum_amount – Amount below minimum.
400: above_maximum_amount – Amount above maximum.
402: insufficient_funds – Insufficient wallet balance.
409: duplicate_request_id – Request ID already exists.
409: duplicate_order – Duplicate order within 3 minutes.
403: rest_forbidden – Unauthorized access.
3. Get Data Variations
Endpoint: GET /api/v2/variations/data
Description: Retrieves available data plan variations for network providers. Optionally filter by service_id.
Authentication: Not required (public endpoint).
Parameters:
Parameter
Type
Required
Description
service_id
String
No
Network provider (mtn, airtel, glo, 9mobile, smile).
Validation:
Service ID must be one of: mtn, airtel, glo, 9mobile, smile.
Sample Request (All Variations):
curl -X GET https://“vtu.ng“/wp-json/api/v2/variations/data
Sample Response (All Variations) – Fetch all variations via the endpoint URL:
{“code”: “success”,”message”: “All Variations Retrieved”,”product”: “Data”,”data”: [{“variation_id”: 5580757,”service_name”: “Glo”,”service_id”: “glo”,”data_plan”: “1.75GB – Sunday”,”price”: “249”,”availability”: “Available”},{“variation_id”: 2660,”service_name”: “Glo”,”service_id”: “glo”,”data_plan”: “2.6GB – 30 Days”,”price”: “1099”,”availability”: “Available”},{“variation_id”: 244597,”service_name”: “MTN”,”service_id”: “mtn”,”data_plan”: “5GB – 7 Days”,”price”: “1599”,”availability”: “Unavailable”},{“variation_id”: 2104621,”service_name”: “Airtel”,”service_id”: “airtel”,”data_plan”: “2GB (Gift) – 30 Days”,”price”: “1999”,”availability”: “Unavailable”},{“variation_id”: 2669,”service_name”: “Airtel”,”service_id”: “airtel”,”data_plan”: “35GB – 30 Days”,”price”: “10499”,”availability”: “Available”},{“variation_id”: 229129,”service_name”: “MTN”,”service_id”: “mtn”,”data_plan”: “10GB – 30 Days”,”price”: “7499”,”availability”: “Unavailable”},{“variation_id”: 2682,”service_name”: “MTN”,”service_id”: “mtn”,”data_plan”: “1GB – 30 Days”,”price”: “799”,”availability”: “Unavailable”},{“variation_id”: 2662,”service_name”: “9mobile”,”service_id”: “9mobile”,”data_plan”: “3.91GB – 30 Days”,”price”: “3199”,”availability”: “Available”},{“variation_id”: 2787,”service_name”: “9mobile”,”service_id”: “9mobile”,”data_plan”: “2.44GB – 30 Days”,”price”: “2099”,”availability”: “Available”}]}
Sample Request (Filtered by MTN):
curl -X GET https://“vtu.ng“/wp-json/api/v2/variations/data?service_id=mtn
Sample Response (Filtered by MTN) – Fetch all variations via the endpoint URL:
{“code”: “success”,”message”: “Mtn Variations Retrieved”,”product”: “Data”,”data”: [{“variation_id”: 244542,”service_name”: “MTN”,”service_id”: “mtn”,”data_plan”: “2GB + 2 mins – 30 Days”,”price”: “1599”,”availability”: “Available”},{“variation_id”: 2676,”service_name”: “MTN”,”service_id”: “mtn”,”data_plan”: “1GB + 5 mins – 7 Days”,”price”: “819”,”availability”: “Available”},{“variation_id”: 2667,”service_name”: “MTN”,”service_id”: “mtn”,”data_plan”: “75GB – 30 Days”,”price”: “19999”,”availability”: “Available”}]}
Error Responses:
400: invalid_service_id – Invalid service ID provided.
404: no_product – Product ID invalid or not variable.
4. Purchase Data: Cheapest Data Reseller API (MTN, Glo, Airtel, T2mobile, & Smile)
Endpoint: POST /api/v2/data
Description: Purchases a data plan for a specified phone number and network provider. It provides you with the ultimate solution to all your data API questions, like CG Data API, MTN SME Data Reseller API, Airtel Corporate Data Gifting API (SME), Glo Corporate Data Gifting API (SME), MTN Corporate Data Gifting API, MTN DataShare API, T2mobile Corporate Data Gifting API (SME), and Smile data API.
Authentication: Required (Bearer token).
Parameters:
Parameter
Type
Required
Description
request_id
String
Yes
Unique identifier (max 50 chars).
phone
String
Yes
Phone number (e.g., 08012345678 or +2348012345678).
service_id
String
Yes
Network provider (mtn, airtel, glo, 9mobile, smile).
variation_id
String
Yes
Data plan’s variation ID (from /api/v2/variations/data).
Validation:
Phone number must match the service provider’s prefix.
Variation ID must be valid (retrieve via /api/v2/variations/data).
Wallet balance must cover the plan’s reseller price.
Sample Request:
curl -X POST https://“vtu.ng“/wp-json/api/v2/data \ -H “Authorization: Bearer your_jwt_token” \ -H “Content-Type: application/json” \ -d ‘{ “request_id”: “req_123456789”, “phone”: “08012345678”, “service_id”: “mtn”, “variation_id”: “2682” }’
Sample Processing Order Response:
{ “code”: “success”, “message”: “ORDER PROCESSING”, “data”: { “order_id”: 12346, “status”: “processing-api”, “product_name”: “Data”, “variation_id”: “2682”, “service_name”: “MTN”, “data_plan”: “1GB (SME) – 30 Days”, “phone”: “08012345678”“, “amount”: 300.00“, “discount”: “50.00”, “amount_charged”: “250.00”, “initial_balance”: “4902.50”, “final_balance”: “4652.50”, “request_id”: “req_123456789” } }
Sample Completed Order Response:
{ “code”: “success”, “message”: “ORDER COMPLETED”, “data”: { “order_id”: 12346, “status”: “completed-api”, “product_name”: “Data”, “variation_id”: “2682”, “service_name”: “MTN”, “data_plan”: “1GB (SME) – 30 Days”, “phone”: “08012345678”“, “amount”: 300.00“, “discount”: “50.00”, “amount_charged”: “250.00”, “initial_balance”: “4902.50”, “final_balance”: “4652.50”, “request_id”: “req_123456789” } }
Sample Refunded Order Response:
{ “code”: “success”, “message”: “ORDER REFUNDED”, “data”: { “order_id”: 12346, “status”: “refunded”, “product_name”: “Data”, “variation_id”: “2682”, “service_name”: “MTN”, “data_plan”: “1GB (SME) – 30 Days”, “phone”: “08012345678”, “amount”: 300.00, “discount”: “00.00”, “amount_charged”: “0.00”, “initial_balance”: “4902.50”, “final_balance”: “4902.50”, “request_id”: “req_123456789” } }
Error Responses:
400: missing_fields – Required parameters missing.
400: invalid_service – Invalid service ID or phone number.
400: invalid_variation_id – Invalid variation ID.
402: insufficient_funds – Insufficient wallet balance.
409: duplicate_request_id – Request ID already exists.
409: duplicate_order – Duplicate order within 3 minutes.
403: rest_forbidden – Unauthorized access.
5. Verify Customer: Meter/Account Number, IUC/Smartcard Number, and Betting ID Verification API
Endpoint: POST /api/v2/verify-customer
Description: Verifies customer details for electricity, cable TV, or betting services. Use this before purchasing electricity, cable TV, or funding betting accounts.
Authentication: Required (Bearer token).
Parameters:
Parameter
Type
Required
Description
customer_id
String
Yes
Customer ID (meter/account number, smartcard number, or betting ID).
service_id
String
Yes
Service provider (dstv, gotv, startimes, ikeja-electric, eko-electric, kano-electric, portharcourt-electric, jos-electric, ibadan-electric, kaduna-electric, abuja-electric, enugu-electric, benin-electric, aba-electric, yola-electric, 1xBet, BangBet, Bet9ja, BetKing, BetLand, BetLion, BetWay, CloudBet, LiveScoreBet, MerryBet, NaijaBet, NairaBet, SupaBet).
variation_id
String
Yes*
Meter type (prepaid, postpaid) for electricity.
*Required only for electricity services.
Sample Request (Electricity):
curl -X POST https://“vtu.ng“/wp-json/api/v2/verify-customer \ -H “Authorization: Bearer your_jwt_token” \ -H “Content-Type: application/json” \ -d ‘{ “customer_id”: “12345678901”, “service_id”: “ikeja-electric”, “variation_id”: “prepaid” }’
Sample Response (Electricity):
{ “code”: “success”, “message”: “Customer Details Retrieved”, “data”: { “service_name”: “Ikeja (IKEDC)”, “customer_id”: “12345678901”, “customer_name”: “Chukwuemeka Ajayi Muhammed”, “customer_address”: “123 Lagos Street”, “customer_arrears”: 0, “outstanding”: 0, “meter_number”: “12345678901”, “account_number”: “2345908905”, “district”: “Ikeja”, “service_band”: “A”, “min_purchase_amount”: 1000, “max_purchase_amount”: 100000, “business_unit”: “Ikeja”, “customer_account_type”: “NMD” } }
Sample Request (Betting):
curl -X POST https://“vtu.ng“/wp-json/api/v2/verify-customer \ -H “Authorization: Bearer your_jwt_token” \ -H “Content-Type: application/json” \ -d ‘{ “customer_id”: “12345”, “service_id”: “Bet9ja” }’
Sample Response (Betting):
{ “code”: “success”, “message”: “Customer Details Retrieved”, “data”: { “service_name”: “Bet9ja”, “customer_id”: “12345”, “customer_name”: “John Smith”, “customer_username”: “jsmith”, “customer_email_address”: “john@example.com”, “customer_phone_number”: “08098765432”, “minimum_amount”: 100, “maximum_amount”: 100000 } }
Sample Request (Cable TV):
curl -X POST https://“vtu.ng“/wp-json/api/v2/verify-customer \ -H “Authorization: Bearer your_jwt_token” \ -H “Content-Type: application/json” \ -d ‘{ “customer_id”: “1234567890”, “service_id”: “dstv” }’
Sample Response (Cable TV):
{ “code”: “success”, “message”: “Customer Details Retrieved”, “data”: { “service_name”: “DStv”, “customer_id”: “1234567890”, “customer_name”: “Aisha Chioma Oreoluwa”, “status”: “Active”, “due_date”: “2025-05-01”, “balance”: 0, “current_bouquet”: “Compact”, “renewal_amount”: 19000 } }
Error Responses:
400: missing_fields – Required parameters missing.
400: invalid_field – Invalid service or variation ID.
400: failure – Verification failed (e.g., invalid customer ID).
403: rest_forbidden – Unauthorized access.
6. Purchase Electricity: Electricity Bills Payment API – Buy Power/Pay Electricity Bills Online in Nigeria
Endpoint: POST /api/v2/electricity
Description: Purchases electricity units for a specified meter/account number. Verify the customer first using /api/v2/verify-customer.
Authentication: Required (Bearer token).
Parameters:
Parameter
Type
Required
Description
request_id
String
Yes
Unique identifier (max 50 chars).
customer_id
String
Yes
Meter or account number.
service_id
String
Yes
Electricity provider (ikeja-electric, eko-electric, kano-electric, portharcourt-electric, jos-electric, ibadan-electric, kaduna-electric, abuja-electric, enugu-electric, benin-electric, aba-electric, yola-electric).ikeja-electric = Ikeja (IKEDC): Lagos State (Ikeja) – Abule Egba, Akowonjo, Ikeja, Ikorodu, Oshodi, Shomolueko-electric = Eko (EKEDC): Lagos State (Eko) – Apapa, Lekki, Ibeju, Island, Agbara, Ojo, Festac, Ijora, Mushin, Orilekano-electric = Kano (KEDCO): Kano State, Katsina State, Jigawa Stateportharcourt-electric = Portharcourt (PHED): Rivers, Akwa Ibom, Bayelsa, Cross Riverjos-electric = Jos (JED): Bauchi, Benue, Gombe, Plateauibadan-electric = Ibadan (IBEDC): Oyo, Ogun, Osun, Kwara, Parts of Niger, Ekiti and Kogi Stateskaduna-electric = Kaduna (KAEDCO): Kaduna| Kebbi| Sokoto| Zamfaraabuja-electric = Abuja (AEDC): Federal Capital Territory (Abuja), Kogi State, Niger State, Nassarawa Stateenugu-electric = Enugu (EEDC): Anambra State, Enugu State, Imo State, Ebonyi Statebenin-electric = Benin (BEDC): Delta, Edo, Ekiti, and Ondo Stateaba-electric = Aba (ABEDC): Abia Stateyola-electric = Yola (YEDC): Adamawa, Taraba, Borno, & Yobe
variation_id
String
Yes
Meter type (prepaid, postpaid).
amount
Integer
Yes
Amount in NGN (min varies, max ₦100,000).
Validation:
Customer ID verified via /api/v2/verify-customer.
Amount must meet minimum purchase and arrears requirements.
Discounts: 0.1% to 1.5% (provider-dependent).
Sample Request:
curl -X POST https://“vtu.ng“/wp-json/api/v2/electricity \ -H “Authorization: Bearer your_jwt_token” \ -H “Content-Type: application/json” \ -d ‘{ “request_id”: “req_123456789”, “customer_id”: “12345678901”, “service_id”: “ikeja-electric”, “variation_id”: “prepaid”, “amount”: 1000 }’
Sample Processing Order Response:
{ “code”: “success”, “message”: “ORDER PROCESSING”, “data”: { “order_id”: 12347, “status”: “processing-api”, “product_name”: “Electricity”, “service_name”: “Ikeja (IKEDC)”, “customer_id”: “12345678901”, “customer_name”: “Chukwuemeka Ajayi Muhammed”, “customer_address”: “123 Lagos Street”, “token”: null, “units”: null, “band”: “A”, “amount”: 10000, “amount_charged”: “9850.00”, “discount”: “150.00”, “initial_balance”: “40652.50”, “final_balance”: “30802.50”, “request_id”: “req_123456789” } }
Sample Completed Order Response:
{ “code”: “success”, “message”: “ORDER COMPLETED”, “data”: { “order_id”: 12347, “status”: “completed-api”, “product_name”: “Electricity”, “service_name”: “Ikeja (IKEDC)”, “customer_id”: “12345678901”, “customer_name”: “Chukwuemeka Ajayi Muhammed”, “customer_address”: “123 Lagos Street”, “token”: “1234-5678-9012-3456”, “units”: “80.5”, “band”: “A”, “amount”: 10000, “amount_charged”: “9850.00”, “discount”: “150.00”, “initial_balance”: “40652.50”, “final_balance”: “30802.50”, “request_id”: “req_123456789” } }
Sample Refunded Order Response:
{ “code”: “success”, “message”: “ORDER REFUNDED”, “data”: { “order_id”: 12347, “status”: “refunded”, “product_name”: “Electricity”, “service_name”: “Ikeja (IKEDC)”, “customer_id”: “12345678901”, “customer_name”: “Chukwuemeka Ajayi Muhammed”, “customer_address”: “123 Lagos Street”, “token”: null, “units”: null, “band”: “A”, “amount”: 10000, “amount_charged”: “0.00”, “discount”: “0.00”, “initial_balance”: “40652.50”, “final_balance”: ““`40652.50“`”, “request_id”: “req_123456789” } }
Error Responses:
400: missing_fields – Required parameters missing.
400: invalid_service_id – Invalid service ID.
400: invalid_variation_id – Invalid variation ID.
400: below_minimum_amount – Amount below minimum.
400: below_customer_arrears – Amount below arrears.
402: insufficient_funds – Insufficient wallet balance.
409: duplicate_request_id – Request ID already exists.
409: duplicate_order – Duplicate order within 3 minutes.
403: rest_forbidden – Unauthorized access.
7. Fund Betting Account: Betting Accounts Funding API in Nigeria
Endpoint: POST /api/v2/betting
Description: Funds a betting account for a specified customer ID. Verify the customer first using /api/v2/verify-customer.
Authentication: Required (Bearer token).
Parameters:
Parameter
Type
Required
Description
request_id
String
Yes
Unique identifier (max 50 chars).
customer_id
String
Yes
Betting account ID.
service_id
String
Yes
Betting provider (1xBet, BangBet, Bet9ja, BetKing, BetLand, BetLion, BetWay, CloudBet, LiveScoreBet, MerryBet, NaijaBet, NairaBet, SupaBet).
amount
Integer
Yes
Amount in NGN (min ₦100, max ₦100,000).
Validation:
Customer ID verified via /api/v2/verify-customer.
Discounts: 0% (some providers), 0.2% (others).
Sample Request:
curl -X POST https://“vtu.ng“/wp-json/api/v2/betting \ -H “Authorization: Bearer your_jwt_token” \ -H “Content-Type: application/json” \ -d ‘{ “request_id”: “req_123456789”, “customer_id”: “12345”, “service_id”: “Bet9ja”, “amount”: 500 }’
Sample Processing Order Response:
{ “code”: “success”, “message”: “ORDER PROCESSING”, “data”: { “order_id”: 12349, “status”: “processing-api”, “product_name”: “Betting”, “service_name”: “Bet9ja”, “customer_id”: “12345”, “customer_name”: “John Smith”, “customer_username”: “jsmith”, “customer_email_address”: “john@example.com”, “customer_phone_number”: “08098765432”, “amount”: 500, “amount_charged”: “500.00”, “discount”: “0.00”, “initial_balance”: “1265.50”, “final_balance”: “765.50”, “request_id”: “req_123456789” } }
Sample Completed Order Response:
{ “code”: “success”, “message”: “ORDER COMPLETED”, “data”: { “order_id”: 12349, “status”: “completed-api”, “product_name”: “Betting”, “service_name”: “Bet9ja”, “customer_id”: “12345”, “customer_name”: “John Smith”, “customer_username”: “jsmith”, “customer_email_address”: “john@example.com”, “customer_phone_number”: “08098765432”, “amount”: 500, “amount_charged”: “500.00”, “discount”: “0.00”, “initial_balance”: “1265.50”, “final_balance”: “765.50”, “request_id”: “req_123456789” } }
Sample Refunded Order Response:
{ “code”: “success”, “message”: “ORDER REFUNDED”, “data”: { “order_id”: 12349, “status”: “refunded”, “product_name”: “Betting”, “service_name”: “Bet9ja”, “customer_id”: “12345”, “customer_name”: “John Smith”, “customer_username”: “jsmith”, “customer_email_address”: “john@example.com”, “customer_phone_number”: “08098765432”, “amount”: 500, “amount_charged”: “0.00”, “discount”: “0.00”, “initial_balance”: “1265.50”, “final_balance”: “1265.50”, “request_id”: “req_123456789” } }
Error Responses:
400: missing_fields – Required parameters missing.
400: invalid_service_id – Invalid service ID.
400: below_minimum_amount – Amount below minimum.
400: above_maximum_amount – Amount above maximum.
402: insufficient_funds – Insufficient wallet balance.
409: duplicate_request_id – Request ID already exists.
409: duplicate_order – Duplicate order within 3 minutes.
403: rest_forbidden – Unauthorized access.
8. Get Cable TV Variations
Endpoint: GET /api/v2/variations/tv
Description: Retrieves available cable TV package variations. Optionally filter by service_id.
Authentication: Not required (public endpoint).
Parameters:
Parameter
Type
Required
Description
service_id
String
No
Cable TV provider (dstv, gotv, startimes, showmax).
Validation:
Service ID must be one of: dstv, gotv, startimes, showmax.
Sample Request (All Variations):
curl -X GET https://vtu.ng/wp-json/api/v2/variations/tv
Sample Response (All Variations) – Fetch all variations via the endpoint URL:
{“code”: “success”,”message”: “All TV Variations Retrieved”,”product”: “Cable TV”,”data”: [{“variation_id”: 2691,”service_name”: “Startimes”,”service_id”: “startimes”,”package_bouquet”: “Smart”,”price”: “5100”,”availability”: “Available”},{“variation_id”: 2693,”service_name”: “Startimes”,”service_id”: “startimes”,”package_bouquet”: “Nova”,”price”: “2100”,”availability”: “Available”},{“variation_id”: 2696,”service_name”: “GOtv”,”service_id”: “gotv”,”package_bouquet”: “Jinja”,”price”: “3900”,”availability”: “Available”},{“variation_id”: 2697,”service_name”: “GOtv”,”service_id”: “gotv”,”package_bouquet”: “Smallie”,”price”: “1900”,”availability”: “Available”},{“variation_id”: 2700,”service_name”: “DStv”,”service_id”: “dstv”,”package_bouquet”: “Compact Plus”,”price”: “30000”,”availability”: “Available”},{“variation_id”: 2705,”service_name”: “DStv”,”service_id”: “dstv”,”package_bouquet”: “Padi”,”price”: “4400”,”availability”: “Available”}]}
Sample Request (Filtered by DStv):
curl -X GET https://vtu.ng/wp-json/api/v2/variations/tv?service_id=dstv
Sample Response (Filtered by DStv) – Fetch all variations via the endpoint URL:
{“code”: “success”,”message”: “Dstv Variations Retrieved”,”product”: “Cable TV”,”data”: [{“variation_id”: 247908,”service_name”: “DStv”,”service_id”: “dstv”,”package_bouquet”: “Premium + Extra View”,”price”: “50500”,”availability”: “Available”},{“variation_id”: 247822,”service_name”: “DStv”,”service_id”: “dstv”,”package_bouquet”: “ExtraView Access”,”price”: “6000”,”availability”: “Available”},{“variation_id”: 2699,”service_name”: “DStv”,”service_id”: “dstv”,”package_bouquet”: “Premium”,”price”: “44500”,”availability”: “Available”},{“variation_id”: 2701,”service_name”: “DStv”,”service_id”: “dstv”,”package_bouquet”: “Compact”,”price”: “19000”,”availability”: “Available”},{“variation_id”: 2704,”service_name”: “DStv”,”service_id”: “dstv”,”package_bouquet”: “Yanga”,”price”: “6000”,”availability”: “Available”}]}
Error Responses:
400: invalid_service_id – Invalid service ID provided.
404: no_product – Product ID invalid or not variable.
9. Purchase Cable TV Subscription: Cable TV Bills Payment API – Purchase/Subscribe Cable TV (DStv, GOtv, Startimes, & Showmax)
Endpoint: POST /api/v2/tv
Description: Purchases a cable TV subscription for a specified smartcard/IUC number. Verify the customer first using /api/v2/verify-customer.
Authentication: Required (Bearer token).
Parameters:
Parameter
Type
Required
Description
request_id
String
Yes
Unique identifier (max 50 chars).
customer_id
String
Yes
Smartcard or IUC number.
service_id
String
Yes
Provider (dstv, gotv, startimes, showmax).
variation_id
String
Yes
Package/bouquet variation ID (from /api/v2/variations/tv).
subscription_type
String
No
The subscription type (change or renew). If not set, it defaults to change. It is recommended that you always specify your subscription type for every request.Applicable to DStv and GOtv only.change: This option enables you to activate a DStv/GOtv decoder afresh or modify its current package/bouquet using the smartcard number. It is designed for new or returning DStv/GOtv subscribers looking to update their bouquet.renew: This option allows you to renew a DStv/GOtv decoder subscription using its smartcard number. This option is strictly for a returning customer who desires to renew his/her current DStv/GOtv package/bouquet. Using this option, there may be a discount on the renewal price [according to the discretion of MultiChoice] as opposed to the actual cost of the customer’s DStv/GOtv package/bouquet.
amount
Integer
No
An amount (eg, 5500) is required for the renew subscription type. Note: You are to first verify the DStv/GOtv smartcard number using the /api/v2/verify-customer endpoint and use the renewal_amount obtained from the /api/v2/verify-customer endpoint as the amount in your request payload.For the change subscription type, the amount defaults to the package price unless a custom amount is set.Applicable to DStv, GOtv and Startimes only.
Validation:
Customer ID verified via /api/v2/verify-customer (except Showmax).
Variation ID must be valid (retrieve via /api/v2/variations/tv).
Discounts: 1% (DStv, GOtv, Showmax), 1.5% (Startimes).
Sample Request:
curl -X POST https://“vtu.ng“/wp-json/api/v2/tv \ -H “Authorization: Bearer your_jwt_token” \ -H “Content-Type: application/json” \ -d ‘{ “request_id”: “req_123456789”, “customer_id”: “1234567890”, “service_id”: “dstv”, “variation_id”: “3713” }’
Sample Processing Order Response:
{ “code”: “success”, “message”: “ORDER PROCESSING”, “data”: { “order_id”: 12348, “status”: “processing-api”, “product_name”: “Cable TV”, “service_name”: “DStv”, “customer_id”: “1234567890”, “customer_name”: “Aisha Chioma Oreoluwa”, “amount”: 10500, “amount_charged”: “10395.00”, “discount”: “105.00”, “initial_balance”: “3660.50”, “final_balance”: “1265.50”, “request_id”: “req_123456789” } }
Sample Completed Order Response:
{ “code”: “success”, “message”: “ORDER COMPLETED”, “data”: { “order_id”: 12348, “status”: “completed-api”, “product_name”: “Cable TV”, “service_name”: “DStv”, “customer_id”: “1234567890”, “customer_name”: “Aisha Chioma Oreoluwa”, “amount”: 10500, “amount_charged”: “10395.00”, “discount”: “105.00”, “initial_balance”: “3660.50”, “final_balance”: “1265.50”, “request_id”: “req_123456789” } }
Sample Refunded Order Response:
{ “code”: “success”, “message”: “ORDER REFUNDED”, “data”: { “order_id”: 12348, “status”: “refunded”, “product_name”: “Cable TV”, “service_name”: “DStv”, “customer_id”: “1234567890”, “customer_name”: “Aisha Chioma Oreoluwa”, “amount”: 10500, “amount_charged”: “0.00”, “discount”: “0.00”, “initial_balance”: “3660.50”, “final_balance”: “3660.50”, “request_id”: “req_123456789” } }
Error Responses:
400: missing_fields – Required parameters missing.
400: invalid_service_id – Invalid service ID.
400: invalid_variation_id – Invalid variation ID.
402: insufficient_funds – Insufficient wallet balance.
409: duplicate_request_id – Request ID already exists.
409: duplicate_order – Duplicate order within 3 minutes.
403: rest_forbidden – Unauthorized access.
10. Purchase ePINs: Recharge Cards Printing API in Nigeria
Endpoint: POST /api/v2/epins
Description: Purchases recharge cards PINs (ePINs) for a specified network provider.
Authentication: Required (Bearer token).
Parameters:
Parameter
Type
Required
Description
request_id
String
Yes
Unique identifier (max 50 chars).
service_id
String
Yes
Network provider (mtn, airtel, glo, 9mobile).
value
Integer
Yes
PIN denomination (100, 200, 500).
quantity
Integer
Yes
Number of PINs (min 1, max 40).
Validation:
Value: 100, 200, or 500.
Quantity: Min 1, Max 40.
Discounts: 4.00% (9mobile), 0.50% (MTN), 1% (others).
Sample Request:
curl -X POST https://“vtu.ng“/wp-json/api/v2/epins \ -H “Authorization: Bearer your_jwt_token” \ -H “Content-Type: application/json” \ -d ‘{ “request_id”: “req_123456789”, “service_id”: “mtn”, “value”: 500, “quantity”: 1 }’
Sample Processing Order Response:
{ “code”: “success”, “message”: “ORDER PROCESSING”, “data”: { “order_id”: 12350, “status”: “processing-api”, “product_name”: “ePINs”, “service_name”: “MTN”, “value”: 500, “quantity”: 1, “amount”: 500, “discount”: “5.00”, “amount_charged”: “495.00”, “initial_balance”: “765.50”, “final_balance”: “270.50”, “request_id”: “req_123456789”, “epins”: null } }
Sample Completed Order Response:
{ “code”: “success”, “message”: “ORDER COMPLETED”, “data”: { “order_id”: 12350, “status”: “completed-api”, “product_name”: “ePINs”, “service_name”: “MTN”, “value”: 500, “quantity”: 1, “amount”: 500, “discount”: “5.00”, “amount_charged”: “495.00”, “initial_balance”: “765.50”, “final_balance”: “270.50”, “request_id”: “req_123456789”, “epins”: [{“amount”:”500″,”pin”:”89656105591652958″,”serial”:”00000033739289258″,”instruction”:”To Recharge: Dial *311*PIN# SEND “}] } }
Sample Refunded Order Response:
{ “code”: “success”, “message”: “ORDER REFUNDED”, “data”: { “order_id”: 12350, “status”: “refunded”, “product_name”: “ePINs”, “service_name”: “MTN”, “value”: 500, “quantity”: 1, “amount”: 500, “discount”: “0.00”, “amount_charged”: “0.00”, “initial_balance”: “765.50”, “final_balance”: “765.50”, “request_id”: “req_123456789”, “epins”: null } }
Error Responses:
400: missing_fields – Required parameters missing.
400: invalid_service – Invalid service ID.
400: invalid_value – Invalid denomination.
400: below_minimum_quantity – Quantity below minimum.
400: above_maximum_quantity – Quantity above maximum.
402: insufficient_funds – Insufficient wallet balance.
409: duplicate_request_id – Request ID already exists.
409: duplicate_order – Duplicate order within 3 minutes.
403: rest_forbidden – Unauthorized access.
11. Requery Order
Endpoint: POST /api/v2/requery
Description: Retrieves the status and details of an order by its request_id.
Authentication: Required (Bearer token).
Parameters:
Parameter
Type
Required
Description
request_id
String
Yes
Unique identifier of the order.
Validation:
Request ID must match an order for the authenticated user.
Returns detailed order information.
Sample Request:
curl -X POST https://“vtu.ng“/wp-json/api/v2/requery \ -H “Authorization: Bearer your_jwt_token” \ -H “Content-Type: application/json” \ -d ‘{ “request_id”: “req_123456789” }’
Sample Processing Order Response (Data):
{“code”:”success”,”message”:”ORDER PROCESSING”,”data”:{“order_id”:355960,”status”:”processing-api”,”product_name”:”Data”,”quantity”:1,”amount”:”98.01″,”amount_charged”:”98.01″,”date_created”:”2025-05-06 20:00:14″,”date_updated”:”2025-05-06 20:00:25″,”request_id”:”1017″,”meta_data”:{“network”:”MTN”,”data-plan”:”110MB – 1 Day”,”phone”:”08106223552″}}}
Sample Completed Order Response (ePINs):
{“code”:”success”,”message”:”ORDER COMPLETED”,”data”:{“order_id”:353817,”status”:”completed-api”,”product_name”:”ePINs”,”quantity”:1,”amount”:”100″,”amount_charged”:”99.00″,”date_created”:”2025-03-12 15:28:20″,”date_updated”:”2025-03-12 15:29:32″,”request_id”:”req_123456789″,”meta_data”:{“network”:”MTN”,”value_denomination”:”100″,”epins_quantity”:”1″},”epins”:[{“Amount”:”100″,”pin”:”51555258498444514″,”serial_number”:”00000032234996019″,”instruction”:”To Recharge: Dial *555*PINNUMBER# SEND “}]}}
Sample Refunded Order Response (Airtime):
{“code”:”success”,”message”:”ORDER REFUNDED”,”data”:{“order_id”:354347,”status”:”refunded”,”product_name”:”Airtime”,”quantity”:1,”amount”:”10″,”amount_charged”:”0.00″,”date_created”:”2025-04-08 19:54:20″,”date_updated”:”2025-04-08 19:54:25″,”request_id”:”req_123456789″,”meta_data”:{“network”:”MTN”,”phone”:”08106223552″}}}
Error Responses:
400: missing_request_id – Request ID not provided.
404: order_not_found – No order found for the request ID.
403: rest_forbidden – Unauthorized access.
Possible Messages:
ORDER COMPLETED – Status is completed-api.
ORDER PROCESSING – Status is processing-api.
ORDER QUEUED – Status is queued-api.
ORDER INITIATED – Status is initiated-api.
ORDER CANCELLED – Status is cancelled.
ORDER PENDING – Status is pending.
ORDER FAILED – Status is failed.
ORDER REFUNDED – Status is refunded.
ORDER ON-HOLD – Status is on-hold.
Webhook Notifications
Description: The VTU.ng API sends webhook notifications to a user-specified URL when an order’s status changes to completed-api or refunded (for orders previously in an API-related status).
Setup:
Configure a valid HTTPS webhook URL on the developer tab of the account settings page.
The URL must accept POST requests.
Trigger Events:
Order Completed: Status changes to completed-api (only when triggered manually by an administrator).
Order Refunded: Status changes to refunded from an API-related status (e.g., initiated-api, processing-api).
Am going to load 2nd part of this integration information