Partner API · v1
Sell data, airtime and bills from your own platform
Connect your website, app or bot to Global Link Data. Every purchase is one HTTPS request with a JSON body, paid from your Global Link Data wallet at your reseller price, and you get the result instantly or by webhook.
Content-Type: application/json) and JSON responsesQuick start
- Log in and open Developer API. Generate a live key (and a sandbox key for testing).
- Call GET /catalog to get plan IDs and your prices.
- Buy with e.g. POST /data, sending your key, the plan ID, the phone number and your transaction PIN.
- Set a webhook URL so you're told when pending orders finish.
Connect without coding
Already run a VTU website? You don't need to write any code to sell our services. Add Global Link Data as a supplier in your own admin panel, exactly like any other supplier — our API speaks the two most common VTU script formats.
YOUR_API_KEY:YOUR_PIN (key, colon, transaction PIN).| Service | Vicom-type URL | MSORG-type URL |
|---|---|---|
| Balance | GET /api/user | GET /api/user/ |
| Airtime | POST /api/airtime | POST /api/topup/ |
| Data | POST /api/data | POST /api/data/ |
| Cable TV | POST /api/cable verify: /api/cable/cable-validation | POST /api/cablesub/ verify: /api/validateiuc |
| Electricity | POST /api/bill verify: /api/bill/bill-validation | POST /api/billpayment/ verify: /api/validatemeter |
| Exam PINs | POST /api/exam | POST /api/epin/ |
| Recharge / data cards | POST /api/recharge_card POST /api/data_card | same |
| Bulk SMS | POST /api/bulk-sms | same |
| Order status | GET /api/transaction/<request-id> | same |
| Plan lists | GET /api/data_plans · /api/get-cable · /api/get-cable-plan?cable=ID · /api/get-bill · /api/get-exam · /api/get-networks | |
- Network numbers follow your script's format. Vicom-type:
1MTN,2AIRTEL,3GLO,49MOBILE. MSORG-type:1MTN,2GLO,3AIRTEL,49MOBILE. Network names ("MTN") work too. - Common field spellings are all accepted (
phoneormobile_number,planordata_plan,iucorsmart_card_number, …), and replies include both formats' fields (status,Status,api_response,msg,request-id,new_balance…). - Send your
request-id(most scripts do) and retries are safe: the same ID returns the original order. Your script's automatic status check (/api/transaction/<request-id>) works too. - Building something new? Use the v1 API below — it is cleaner and fully documented here.
Authentication
Send your API key as a Bearer token on every request:
Authorization: Bearer YOUR_API_KEY
- Live keys look like
gld_live_…and spend real money. Sandbox keys start withsandbox_and are fully simulated (details). Same URL — the key decides. - Purchases need your transaction PIN in the body (
"pin") — unless your key is locked to your server's IP addresses. Three wrong PINs lock purchases for 10 minutes. - IP allowlist (recommended): on the Developer API page, list your server's IP addresses. Requests from any other IP are refused, and purchases from your listed IPs no longer need the PIN — the simplest and safest setup for a server-to-server integration.
- Keys don't expire. If one leaks, regenerate it on the Developer API page — the old key stops working at once.
Responses & errors
Every response has a status field. A purchase ends in one of four ways — handle all four:
{
"status": "success",
"message": "Data purchase successful",
"data": {
"reference": "GLDE213826EA8181791581826",
"balance_after": 44770
}
}
Delivered. Keep the reference.
{
"status": "pending",
"success": true,
"pending": true,
"message": "Your order is processing.",
"data": {
"reference": "GLDE213826EA8181791581826"
}
}
Accepted and charged; the provider hasn't confirmed yet. Wait for the webhook or poll /receipt. Do not retry.
{
"status": "error",
"success": false,
"message": "Transaction failed. Your wallet has been refunded.",
"refunded": true,
"reference": "GLDE213826EA8181791581826"
}
The provider declined it and your wallet was refunded. Safe to retry.
{
"status": "error",
"message": "Invalid transaction PIN"
}
Nothing was charged. Fix the request and try again.
| HTTP | Meaning |
|---|---|
| 200 | Success or pending (check status). |
| 400 | Missing/invalid field, wrong PIN, insufficient balance ("Insufficient balance. Please top up your wallet."), or declined and refunded. |
| 401 | Missing, wrong or revoked API key, or suspended account. |
| 403 | "API access requires a vendor account.", API access disabled for the account, request from an IP not on your allowlist, or the service is switched off. |
| 404 | Unknown transaction reference (/receipt). |
| 405 | Wrong method (e.g. GET on a POST endpoint). |
| 429 | Too many requests — slow down (limits). |
| 500 | Our error. Check /receipt before retrying a purchase. |
Limits & safety
- Purchases: 30 per minute per account. Smartcard/meter lookups: 20 per 5 minutes. Bulk SMS: 20 requests per hour.
- Duplicate protection: an identical purchase (same service, number and amount/plan) within 5 seconds is refused with "Duplicate request detected" and not charged.
- Retrying safely: send your own
request_idwith each purchase. If a request times out, send it again with the same ID — you get the original order back ("duplicate": true) instead of buying twice. Without a request_id, check /transactions before retrying. - Your account's daily spending limits and per-purchase limits also apply.
Sandbox testing
- Use your
sandbox_…key against the same base URL. Nothing real is bought and your live wallet is untouched. - Your sandbox wallet starts with ₦1,000,000 (reset it any time on the Developer API page).
- Purchases succeed instantly — unless the phone, meter or smartcard number ends in
0000, which simulates a decline and refund so you can test that path. - Sandbox purchases also send webhooks, marked
"sandbox": true.
Wallet balance
Your wallet and cashback balance. With a sandbox key it returns the simulated sandbox balance.
curl "https://globallinkdata.com.ng/api/v1/balance" \
-H "Authorization: Bearer YOUR_API_KEY"
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/balance");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/balance", {
headers: {
"Authorization": "Bearer YOUR_API_KEY"
}
});
const data = await res.json();
console.log(data);
import requests
res = requests.get(
"https://globallinkdata.com.ng/api/v1/balance",
headers={"Authorization": "Bearer YOUR_API_KEY"},
timeout=60,
)
print(res.json())
{
"status": "success",
"data": {
"wallet_balance": 45250,
"cashback_balance": 120.5,
"currency": "NGN",
"sandbox": false
}
}
Catalog & your prices
Every active data plan, cable plan, electricity company and exam type, priced for your account tier. Call it to build your own price list, and cache it — prices change rarely.
Use data_plans[].id as plan_id when buying data, cable_plans[].id as plan_id for cable TV, cable_plans[].cable_id when validating a smartcard, and discos[].disco_id for electricity. The plan_id field inside a catalog row is our internal supplier code — don't send it.
curl "https://globallinkdata.com.ng/api/v1/catalog" \
-H "Authorization: Bearer YOUR_API_KEY"
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/catalog");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/catalog", {
headers: {
"Authorization": "Bearer YOUR_API_KEY"
}
});
const data = await res.json();
console.log(data);
import requests
res = requests.get(
"https://globallinkdata.com.ng/api/v1/catalog",
headers={"Authorization": "Bearer YOUR_API_KEY"},
timeout=60,
)
print(res.json())
{
"status": "success",
"data": {
"data_plans": [
{
"id": 209,
"network": "GLO",
"plan_id": "0",
"plan_name": "1 GB",
"display_name": "1GB",
"data_type": "SME",
"data_size": "1GB",
"validity": "14 Days",
"selling_price": "480.00",
"network_id": 2,
"cashback_type": null,
"cashback_value": null
}
],
"cable_plans": [
{
"id": 12,
"provider": "GOTV",
"cable_id": "1",
"plan_id": "gotv-max",
"plan_name": "GOtv Max",
"display_name": "GOtv Max",
"selling_price": "8500.00"
}
],
"discos": [
{
"disco_id": 1,
"disco_name": "Ikeja Electric",
"charge": "0.00"
}
],
"exam_prices": {
"WAEC": 3500,
"NECO": 1200
},
"recharge_card_prices": {
"MTN": {
"100": 98
}
},
"bulk_sms_pricing": {
"price_per_page": 3,
"discount_percent": 0,
"cost_price": 2.5
},
"cashback": {
"airtime": null
}
}
}
Buy data
Sends a data bundle to a phone number.
| Body field | Type | Required | Description |
|---|---|---|---|
| plan_id | integer | Yes | The plan's id from GET /catalog or the data plan table. |
| phone | string | Yes | 11-digit Nigerian number, e.g. 08031234567. |
| pin | string | Yes | The account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details). |
| request_id | string | No | Your own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase. |
| network | string | No | Ignored — the plan decides the network. Accepted for older integrations. |
| ported | boolean | No | Send true for a number ported to another network, to skip the number-prefix check. |
curl -X POST "https://globallinkdata.com.ng/api/v1/data" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"plan_id":209,"phone":"08157038870","pin":"12345","request_id":"order-1002"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/data");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'plan_id' => 209,
'phone' => '08157038870',
'pin' => '12345',
'request_id' => 'order-1002',
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/data", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"plan_id": 209,
"phone": "08157038870",
"pin": "12345",
"request_id": "order-1002"
})
});
const data = await res.json();
console.log(data);
import requests
res = requests.post(
"https://globallinkdata.com.ng/api/v1/data",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"plan_id":209,"phone":"08157038870","pin":"12345","request_id":"order-1002"},
timeout=60,
)
print(res.json())
{
"status": "success",
"message": "Data purchase successful",
"data": {
"reference": "GLDE213826EA8181791581826",
"cashback_earned": 0,
"balance_before": 45250,
"balance_after": 44770
}
}
{
"status": "error",
"message": "This number looks like an MTN line, not GLO. If it was ported, set ported to true."
}
Buy airtime
Tops up a phone number. Your discount is applied automatically, so you are charged less than the face value.
| Body field | Type | Required | Description |
|---|---|---|---|
| network | string | Yes | MTN, GLO, AIRTEL or 9MOBILE. |
| phone | string | Yes | 11-digit Nigerian number. |
| amount | number | Yes | Face value in naira. Default limits ₦50 – ₦10,000 per purchase. |
| pin | string | Yes | The account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details). |
| ported | boolean | No | Send true for a number ported to another network, to skip the number-prefix check. |
| request_id | string | No | Your own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase. |
curl -X POST "https://globallinkdata.com.ng/api/v1/airtime" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"network":"MTN","phone":"08031234567","amount":100,"pin":"12345","request_id":"order-1001"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/airtime");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'network' => 'MTN',
'phone' => '08031234567',
'amount' => 100,
'pin' => '12345',
'request_id' => 'order-1001',
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/airtime", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"network": "MTN",
"phone": "08031234567",
"amount": 100,
"pin": "12345",
"request_id": "order-1001"
})
});
const data = await res.json();
console.log(data);
import requests
res = requests.post(
"https://globallinkdata.com.ng/api/v1/airtime",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"network":"MTN","phone":"08031234567","amount":100,"pin":"12345","request_id":"order-1001"},
timeout=60,
)
print(res.json())
{
"status": "success",
"message": "Airtime purchase successful",
"data": {
"reference": "GLDE213826EA8181791581826",
"cashback_earned": 0,
"balance_before": 45250,
"balance_after": 45151
}
}
Verify a smartcard / IUC
Looks up the customer name on a DStv, GOtv or Startimes card before you charge them. Free, but limited to 20 lookups per 5 minutes.
| Body field | Type | Required | Description |
|---|---|---|---|
| cable_id | integer | Yes | The provider: cable_plans[].cable_id from the catalog (see the cable table). |
| smartcard | string | Yes | Smartcard / IUC number. |
curl -X POST "https://globallinkdata.com.ng/api/v1/validate-cable" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cable_id":1,"smartcard":"7023456789"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/validate-cable");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'cable_id' => 1,
'smartcard' => '7023456789',
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/validate-cable", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"cable_id": 1,
"smartcard": "7023456789"
})
});
const data = await res.json();
console.log(data);
import requests
res = requests.post(
"https://globallinkdata.com.ng/api/v1/validate-cable",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"cable_id":1,"smartcard":"7023456789"},
timeout=60,
)
print(res.json())
{
"status": "success",
"success": true,
"message": "SmartCard verified successfully.",
"data": {
"name": "IBRAHIM MUSA",
"customer_name": "IBRAHIM MUSA",
"smartcard": "7023456789"
}
}
Pay for cable TV
Renews or changes a DStv, GOtv or Startimes subscription.
| Body field | Type | Required | Description |
|---|---|---|---|
| plan_id | integer | Yes | The package's id from cable_plans (catalog or cable table). The package decides the provider. |
| smartcard | string | Yes | Smartcard / IUC number. Verify it first with /validate-cable. |
| pin | string | Yes | The account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details). |
| request_id | string | No | Your own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase. |
| phone | string | No | Customer's phone number, passed to the provider. |
curl -X POST "https://globallinkdata.com.ng/api/v1/cabletv" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"plan_id":12,"smartcard":"7023456789","pin":"12345"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/cabletv");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'plan_id' => 12,
'smartcard' => '7023456789',
'pin' => '12345',
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/cabletv", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"plan_id": 12,
"smartcard": "7023456789",
"pin": "12345"
})
});
const data = await res.json();
console.log(data);
import requests
res = requests.post(
"https://globallinkdata.com.ng/api/v1/cabletv",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"plan_id":12,"smartcard":"7023456789","pin":"12345"},
timeout=60,
)
print(res.json())
{
"status": "success",
"message": "Subscription successful",
"data": {
"reference": "GLDE213826EA8181791581826",
"cashback_earned": 0,
"balance_before": 45250,
"balance_after": 36750
}
}
Verify a meter
Looks up the customer name and address on a meter. Free, 20 lookups per 5 minutes.
| Body field | Type | Required | Description |
|---|---|---|---|
| disco_id | integer | Yes | The electricity company's disco_id (catalog or disco table). |
| meter_number | string | Yes | Meter number. |
| meter_type | string | Yes | prepaid or postpaid. |
curl -X POST "https://globallinkdata.com.ng/api/v1/validate-meter" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"disco_id":1,"meter_number":"45031234567","meter_type":"prepaid"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/validate-meter");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'disco_id' => 1,
'meter_number' => '45031234567',
'meter_type' => 'prepaid',
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/validate-meter", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"disco_id": 1,
"meter_number": "45031234567",
"meter_type": "prepaid"
})
});
const data = await res.json();
console.log(data);
import requests
res = requests.post(
"https://globallinkdata.com.ng/api/v1/validate-meter",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"disco_id":1,"meter_number":"45031234567","meter_type":"prepaid"},
timeout=60,
)
print(res.json())
{
"status": "success",
"data": {
"name": "ADEBAYO OLU",
"address": "12 Allen Avenue, Ikeja"
}
}
Pay an electricity bill
Buys prepaid units (returns the token) or pays a postpaid bill.
| Body field | Type | Required | Description |
|---|---|---|---|
| disco_id | integer | Yes | The electricity company's disco_id. |
| meter_number | string | Yes | Meter number. Verify it first with /validate-meter. |
| meter_type | string | Yes | prepaid or postpaid. |
| amount | number | Yes | Amount in naira. Default limits ₦1,000 – ₦50,000. A company may add a service charge (see discos[].charge). |
| pin | string | Yes | The account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details). |
| request_id | string | No | Your own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase. |
| phone | string | No | Customer's phone number. |
If the provider is slow, the response has "status": "pending" and "token": null; the token arrives in your webhook and in /receipt.
curl -X POST "https://globallinkdata.com.ng/api/v1/electricity" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"disco_id":1,"meter_number":"45031234567","meter_type":"prepaid","amount":2000,"pin":"12345"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/electricity");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'disco_id' => 1,
'meter_number' => '45031234567',
'meter_type' => 'prepaid',
'amount' => 2000,
'pin' => '12345',
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/electricity", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"disco_id": 1,
"meter_number": "45031234567",
"meter_type": "prepaid",
"amount": 2000,
"pin": "12345"
})
});
const data = await res.json();
console.log(data);
import requests
res = requests.post(
"https://globallinkdata.com.ng/api/v1/electricity",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"disco_id":1,"meter_number":"45031234567","meter_type":"prepaid","amount":2000,"pin":"12345"},
timeout=60,
)
print(res.json())
{
"status": "success",
"success": true,
"message": "Electricity purchase successful",
"pending": false,
"data": {
"reference": "GLDE213826EA8181791581826",
"token": "1234-5678-9012-3456-7890",
"cashback_earned": 0,
"pending": false,
"balance_before": 45250,
"balance_after": 43250
}
}
Buy exam PINs
WAEC, NECO, NABTEB and other result-checker PINs.
| Body field | Type | Required | Description |
|---|---|---|---|
| exam_name | string | Yes | Exam code, e.g. WAEC, NECO, NABTEB (see the exam table). |
| quantity | integer | Yes | 1 – 5 PINs. |
| pin | string | Yes | The account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details). |
| request_id | string | No | Your own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase. |
curl -X POST "https://globallinkdata.com.ng/api/v1/exam_pin" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"exam_name":"WAEC","quantity":1,"pin":"12345"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/exam_pin");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'exam_name' => 'WAEC',
'quantity' => 1,
'pin' => '12345',
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/exam_pin", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"exam_name": "WAEC",
"quantity": 1,
"pin": "12345"
})
});
const data = await res.json();
console.log(data);
import requests
res = requests.post(
"https://globallinkdata.com.ng/api/v1/exam_pin",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"exam_name":"WAEC","quantity":1,"pin":"12345"},
timeout=60,
)
print(res.json())
{
"status": "success",
"message": "Exam PIN purchase successful",
"data": {
"reference": "GLDE213826EA8181791581826",
"pins": [
{
"pin": "123456789012",
"serial": "WRN123456789"
}
],
"balance_before": 45250,
"balance_after": 41750
}
}
Print recharge cards
Generates airtime PINs to print or resell. GET /recharge-card returns the networks and the denominations currently available.
| Body field | Type | Required | Description |
|---|---|---|---|
| network_id | integer | Yes | 1 MTN, 2 GLO, 3 AIRTEL, 4 9MOBILE. |
| denomination | integer | Yes | Card value, e.g. 100, 200, 500 — must be one returned by GET /recharge-card. |
| quantity | integer | No | 1 – 50 cards (default 1). |
| pin | string | Yes | The account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details). |
| request_id | string | No | Your own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase. |
| name_on_card | string | No | Business name printed on the cards (max 30 characters). |
curl -X POST "https://globallinkdata.com.ng/api/v1/recharge-card" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"network_id":1,"denomination":100,"quantity":2,"pin":"12345","name_on_card":"My Shop"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/recharge-card");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'network_id' => 1,
'denomination' => 100,
'quantity' => 2,
'pin' => '12345',
'name_on_card' => 'My Shop',
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/recharge-card", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"network_id": 1,
"denomination": 100,
"quantity": 2,
"pin": "12345",
"name_on_card": "My Shop"
})
});
const data = await res.json();
console.log(data);
import requests
res = requests.post(
"https://globallinkdata.com.ng/api/v1/recharge-card",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"network_id":1,"denomination":100,"quantity":2,"pin":"12345","name_on_card":"My Shop"},
timeout=60,
)
print(res.json())
{
"status": "success",
"message": "2 card(s) ready",
"data": {
"reference": "GLDE213826EA8181791581826",
"pins": [
{
"pin": "1234567890123456",
"serial": "SN0001"
},
{
"pin": "6543210987654321",
"serial": "SN0002"
}
]
}
}
Print data cards
Generates data PINs. GET /data-card?net=MTN lists the plans for a network.
| Body field | Type | Required | Description |
|---|---|---|---|
| plan_id | integer | Yes | Data plan id (same IDs as /data). |
| quantity | integer | No | 1 – 5 cards (default 1). |
| pin | string | Yes | The account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details). |
| request_id | string | No | Your own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase. |
curl -X POST "https://globallinkdata.com.ng/api/v1/data-card" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"plan_id":209,"quantity":1,"pin":"12345"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/data-card");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'plan_id' => 209,
'quantity' => 1,
'pin' => '12345',
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/data-card", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"plan_id": 209,
"quantity": 1,
"pin": "12345"
})
});
const data = await res.json();
console.log(data);
import requests
res = requests.post(
"https://globallinkdata.com.ng/api/v1/data-card",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"plan_id":209,"quantity":1,"pin":"12345"},
timeout=60,
)
print(res.json())
{
"status": "success",
"message": "1 card(s) ready",
"data": {
"reference": "GLDE213826EA8181791581826",
"pins": [
{
"pin": "1234567890",
"serial": "DC0001"
}
]
}
}
Send bulk SMS
Sends one message to many numbers. Limited to 20 requests per hour.
| Body field | Type | Required | Description |
|---|---|---|---|
| sender_id | string | Yes | Sender name shown on phones, max 11 characters. |
| message | string | Yes | The message. Long messages are charged per page (see bulk_sms_pricing in the catalog). |
| recipients | array or string | Yes | Phone numbers as a JSON array, or one string separated by commas or new lines. |
| pin | string | Yes | The account's 4–6 digit transaction PIN (the one set on the website or app). Three wrong PINs lock purchases for 10 minutes. Not needed if your key is locked to your server IPs (details). |
| request_id | string | No | Your own unique order ID (max 64 characters). Send the same ID again — e.g. after a timeout — and you get the original order back (with "duplicate": true) instead of a second purchase. |
curl -X POST "https://globallinkdata.com.ng/api/v1/bulk_sms" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"sender_id":"MyShop","message":"Your order is ready.","recipients":["08031234567","08051234567"],"pin":"12345"}'
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/bulk_sms");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'sender_id' => 'MyShop',
'message' => 'Your order is ready.',
'recipients' => [
'08031234567',
'08051234567',
],
'pin' => '12345',
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/bulk_sms", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
"sender_id": "MyShop",
"message": "Your order is ready.",
"recipients": [
"08031234567",
"08051234567"
],
"pin": "12345"
})
});
const data = await res.json();
console.log(data);
import requests
res = requests.post(
"https://globallinkdata.com.ng/api/v1/bulk_sms",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"sender_id":"MyShop","message":"Your order is ready.","recipients":["08031234567","08051234567"],"pin":"12345"},
timeout=60,
)
print(res.json())
{
"status": "success",
"message": "SMS sent to 2 recipient(s)",
"data": {
"reference": "GLDE213826EA8181791581826"
}
}
Transaction status
Gets one transaction by its reference — use it to check a pending order. If an order has been pending for over 2 minutes, this call also re-checks it with the provider before answering.
| Query parameter | Type | Required | Description |
|---|---|---|---|
| reference | string | Yes | The reference you got when buying (query string). |
data.status is success, pending, processing or failed. A failed order is refunded to your wallet automatically.
curl "https://globallinkdata.com.ng/api/v1/receipt?reference=GLDE213826EA8181791581826" \
-H "Authorization: Bearer YOUR_API_KEY"
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/receipt?reference=GLDE213826EA8181791581826");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/receipt?reference=GLDE213826EA8181791581826", {
headers: {
"Authorization": "Bearer YOUR_API_KEY"
}
});
const data = await res.json();
console.log(data);
import requests
res = requests.get(
"https://globallinkdata.com.ng/api/v1/receipt",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"reference":"GLDE213826EA8181791581826"},
timeout=60,
)
print(res.json())
{
"status": "success",
"success": true,
"data": {
"id": 95,
"reference": "GLDE213826EA8181791581826",
"transaction_ref": "GLDE213826EA8181791581826",
"category": "data",
"type": "debit",
"amount": 480,
"status": "success",
"description": "GLO 1 GB for 08157038870",
"phone": "08157038870",
"balance_before": 45250,
"balance_after": 44770,
"charge": 0,
"provider_ref": null,
"created_at": "2026-10-09 22:37:06",
"pins": [],
"token": null,
"meta": {},
"recharge_card": null
}
}
{
"status": "error",
"success": false,
"message": "Transaction not found."
}
Transaction history
Your transactions, newest first.
| Query parameter | Type | Required | Description |
|---|---|---|---|
| page | integer | No | Page number (default 1). |
| per_page | integer | No | 10 – 50 (default 20). |
| filter | string | No | Only one service, e.g. data, airtime, cabletv, electricity. |
curl "https://globallinkdata.com.ng/api/v1/transactions?page=1&per_page=20&filter=data" \
-H "Authorization: Bearer YOUR_API_KEY"
<?php
$ch = curl_init("https://globallinkdata.com.ng/api/v1/transactions?page=1&per_page=20&filter=data");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
const res = await fetch("https://globallinkdata.com.ng/api/v1/transactions?page=1&per_page=20&filter=data", {
headers: {
"Authorization": "Bearer YOUR_API_KEY"
}
});
const data = await res.json();
console.log(data);
import requests
res = requests.get(
"https://globallinkdata.com.ng/api/v1/transactions",
headers={"Authorization": "Bearer YOUR_API_KEY"},
params={"page":1,"per_page":20,"filter":"data"},
timeout=60,
)
print(res.json())
{
"status": "success",
"data": {
"transactions": [
{
"id": 95,
"transaction_ref": "GLDE213826EA8181791581826",
"category": "data",
"amount": "480.00",
"status": "success",
"description": "GLO 1 GB for 08157038870",
"created_at": "2026-10-09 22:37:06"
}
],
"total": 1,
"page": 1,
"per_page": 20,
"total_pages": 1
}
}
Webhooks
Set an HTTPS URL on the Developer API page. When any of your purchases reaches a final result — success, or failed (refunded) — we POST it to you. This is how you learn the outcome of a pending order without polling.
| Header | Value |
|---|---|
| X-Webhook-Event | transaction.success or transaction.failed |
| X-Webhook-Signature | HMAC-SHA256 of the raw request body, using your live API key as the secret (your sandbox key if you have no live key), in hex. Check it before trusting the body. |
| Content-Type | application/json |
{
"event": "transaction.success",
"reference": "GLDE213826EA8181791581826",
"status": "success",
"service": "electricity",
"amount": 2000,
"description": "Ikeja Electric prepaid ₦2,000 for 45031234567",
"phone": "08031234567",
"network": null,
"refunded": false,
"balance_after": 43250,
"sandbox": false,
"created_at": "2026-10-09T22:37:06+01:00",
"timestamp": "2026-10-09T22:37:40+01:00",
"token": "1234-5678-9012-3456-7890"
}
- Reply with any 2xx status within 10 seconds. Otherwise we retry after 1, 5, 15, 60 and 240 minutes, then stop.
- You may receive the same event more than once — use
referenceto ignore repeats. - Failed orders include a
message; electricity includes thetoken. If you regenerate your live key, signatures use the new key.
Verifying the signature
<?php
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, 'YOUR_LIVE_API_KEY');
if (!hash_equals($expected, $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '')) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
// Look up your order by $event['reference'] and mark it success / failed.
http_response_code(200);
const crypto = require("crypto");
const express = require("express");
const app = express();
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const expected = crypto.createHmac("sha256", process.env.LIVE_API_KEY)
.update(req.body).digest("hex");
const got = req.get("X-Webhook-Signature") || "";
if (got.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body);
// Look up your order by event.reference and mark it success / failed.
res.sendStatus(200);
});
import hashlib, hmac, json
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/webhook")
def webhook():
body = request.get_data()
expected = hmac.new(b"YOUR_LIVE_API_KEY", body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Webhook-Signature", "")):
abort(401)
event = json.loads(body)
# Look up your order by event["reference"] and mark it success / failed.
return "", 200
Data plan IDs
Live list of active plans. Send the ID as plan_id. Your prices are in GET /catalog.
GLO (1 plans)
| ID | Plan | Type | Validity |
|---|---|---|---|
| 209 | 1 GB | SME | 14 Days |
Cable plan IDs
Send the package ID as plan_id to /cabletv, and the provider's cable_id to /validate-cable.
No cable plans are active right now.
Electricity company IDs
Send as disco_id to /validate-meter and /electricity.
No electricity companies are active right now.
Exam types
Send the code as exam_name to /exam_pin.
| Code | Exam |
|---|---|
| WAEC | West African Examinations Council |
| NECO | National Examinations Council |
| NABTEB | National Business and Technical Examinations Board |
