Integrating SMS functionality into your application unlocks powerful capabilities: send OTPs for authentication, notify customers of order updates, deliver marketing campaigns programmatically, and automate critical alerts. Our REST API makes it simple to send SMS from any application, whether you're building with PHP, Python, JavaScript, or any language that supports HTTP requests.
This comprehensive tutorial walks you through integrating the BulkSMS Nigeria API step-by-step, from authentication to sending your first message, handling responses, and implementing best practices. By the end, you'll have a fully functional SMS integration ready for production use.
What You'll Learn
API Specifications:
https://www.bulksmsnigeria.com/api/v2abc123xyz789...
Security Warning
Your API token grants full access to your account. Store it in environment variables or secure configuration files, never hardcode it in your source code. Rotate tokens periodically and immediately if compromised.
Let's send a test SMS using cURL to understand the basic request structure:
curl -X POST https://www.bulksmsnigeria.com/api/v2/sms \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"sender": "YourBrand",
"message": "Hello! This is a test message from BulkSMS Nigeria API.",
"recipients": "2348012345678"
}'
Replace YOUR_API_TOKEN with your actual token and 2348012345678 with a valid Nigerian phone number.
{
"status": "success",
"message": "Message sent successfully",
"data": {
"message_id": "msg_abc123xyz789",
"total_recipients": 1,
"total_cost": 6.00,
"balance_after": 494.00,
"recipients": [
{
"phone": "2348012345678",
"status": "queued",
"message_id": "msg_abc123xyz789_001"
}
]
}
}
Choose your programming language and follow the implementation guide:
<?php
class BulkSMSNigeria {
private $apiToken;
private $baseUrl = 'https://www.bulksmsnigeria.com/api/v2';
public function __construct($apiToken) {
$this->apiToken = $apiToken;
}
public function sendSMS($sender, $message, $recipients) {
$url = $this->baseUrl . '/sms';
// Prepare data
$data = [
'sender' => $sender,
'message' => $message,
'recipients' => $recipients
];
// Initialize cURL
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->apiToken,
'Content-Type: application/json',
'Accept: application/json'
]);
// Execute request
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Parse response
$result = json_decode($response, true);
if ($httpCode === 200 && $result['status'] === 'success') {
return $result;
} else {
throw new Exception($result['message'] ?? 'SMS sending failed');
}
}
public function getBalance() {
$url = $this->baseUrl . '/balance';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $this->apiToken,
'Accept: application/json'
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
}
// Usage Example
try {
$sms = new BulkSMSNigeria('YOUR_API_TOKEN');
$result = $sms->sendSMS(
'YourBrand', // Sender ID
'Hello from PHP!', // Message
'2348012345678' // Recipient(s)
);
echo "Message sent! ID: " . $result['data']['message_id'];
echo "\nCost: β¦" . $result['data']['total_cost'];
echo "\nBalance: β¦" . $result['data']['balance_after'];
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
?>
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class BulkSMSClient {
private $client;
public function __construct($apiToken) {
$this->client = new Client([
'base_uri' => 'https://www.bulksmsnigeria.com/api/v2/',
'headers' => [
'Authorization' => 'Bearer ' . $apiToken,
'Content-Type' => 'application/json',
'Accept' => 'application/json'
],
'timeout' => 30
]);
}
public function sendSMS($sender, $message, $recipients) {
try {
$response = $this->client->post('sms', [
'json' => [
'sender' => $sender,
'message' => $message,
'recipients' => $recipients
]
]);
return json_decode($response->getBody(), true);
} catch (RequestException $e) {
if ($e->hasResponse()) {
$error = json_decode($e->getResponse()->getBody(), true);
throw new Exception($error['message'] ?? 'Request failed');
}
throw $e;
}
}
}
// Usage
$sms = new BulkSMSClient('YOUR_API_TOKEN');
$result = $sms->sendSMS('YourBrand', 'Hello from Guzzle!', '2348012345678');
print_r($result);
?>
import requests
import json
class BulkSMSNigeria:
def __init__(self, api_token):
self.api_token = api_token
self.base_url = 'https://www.bulksmsnigeria.com/api/v2'
self.headers = {
'Authorization': f'Bearer {api_token}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}
def send_sms(self, sender, message, recipients):
"""
Send SMS to one or more recipients
Args:
sender (str): Sender ID (max 11 characters)
message (str): Message content
recipients (str or list): Phone number(s) - can be string or list
Returns:
dict: API response with message ID, cost, etc.
"""
url = f'{self.base_url}/sms'
# Convert list to comma-separated string if needed
if isinstance(recipients, list):
recipients = ','.join(recipients)
payload = {
'sender': sender,
'message': message,
'recipients': recipients
}
try:
response = requests.post(
url,
headers=self.headers,
json=payload,
timeout=30
)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
error_data = e.response.json() if e.response else {}
raise Exception(error_data.get('message', 'SMS sending failed'))
except requests.exceptions.RequestException as e:
raise Exception(f'Network error: {str(e)}')
def get_balance(self):
"""Get current account balance"""
url = f'{self.base_url}/balance'
try:
response = requests.get(url, headers=self.headers, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f'Failed to get balance: {str(e)}')
def get_delivery_report(self, message_id):
"""Get delivery report for a specific message"""
url = f'{self.base_url}/delivery/{message_id}'
try:
response = requests.get(url, headers=self.headers, timeout=10)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
raise Exception(f'Failed to get delivery report: {str(e)}')
# Usage Example
if __name__ == '__main__':
# Initialize client
sms = BulkSMSNigeria('YOUR_API_TOKEN')
try:
# Send single SMS
result = sms.send_sms(
sender='YourBrand',
message='Hello from Python! This is a test message.',
recipients='2348012345678'
)
print(f"β Message sent successfully!")
print(f" Message ID: {result['data']['message_id']}")
print(f" Cost: β¦{result['data']['total_cost']}")
print(f" Balance: β¦{result['data']['balance_after']}")
# Send to multiple recipients
bulk_result = sms.send_sms(
sender='YourBrand',
message='Bulk message to multiple recipients',
recipients=['2348012345678', '2348087654321', '2349012345678']
)
print(f"\nβ Bulk message sent to {bulk_result['data']['total_recipients']} recipients")
# Check balance
balance = sms.get_balance()
print(f"\nCurrent Balance: β¦{balance['data']['balance']}")
except Exception as e:
print(f"β Error: {e}")
const axios = require('axios');
class BulkSMSNigeria {
constructor(apiToken) {
this.apiToken = apiToken;
this.baseUrl = 'https://www.bulksmsnigeria.com/api/v2';
this.client = axios.create({
baseURL: this.baseUrl,
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
timeout: 30000
});
}
async sendSMS(sender, message, recipients) {
try {
// Convert array to comma-separated string if needed
if (Array.isArray(recipients)) {
recipients = recipients.join(',');
}
const response = await this.client.post('/sms', {
sender,
message,
recipients
});
return response.data;
} catch (error) {
if (error.response) {
// Server responded with error
throw new Error(error.response.data.message || 'SMS sending failed');
} else if (error.request) {
// Request made but no response
throw new Error('No response from server');
} else {
// Request setup error
throw new Error(error.message);
}
}
}
async getBalance() {
try {
const response = await this.client.get('/balance');
return response.data;
} catch (error) {
throw new Error('Failed to get balance: ' + error.message);
}
}
async getDeliveryReport(messageId) {
try {
const response = await this.client.get(`/delivery/${messageId}`);
return response.data;
} catch (error) {
throw new Error('Failed to get delivery report: ' + error.message);
}
}
}
// Usage Example
(async () => {
const sms = new BulkSMSNigeria('YOUR_API_TOKEN');
try {
// Send single SMS
const result = await sms.sendSMS(
'YourBrand',
'Hello from Node.js! This is a test message.',
'2348012345678'
);
console.log('β Message sent successfully!');
console.log(` Message ID: ${result.data.message_id}`);
console.log(` Cost: β¦${result.data.total_cost}`);
console.log(` Balance: β¦${result.data.balance_after}`);
// Send to multiple recipients
const bulkResult = await sms.sendSMS(
'YourBrand',
'Bulk message to multiple recipients',
['2348012345678', '2348087654321', '2349012345678']
);
console.log(`\nβ Bulk message sent to ${bulkResult.data.total_recipients} recipients`);
// Check balance
const balance = await sms.getBalance();
console.log(`\nCurrent Balance: β¦${balance.data.balance}`);
} catch (error) {
console.error('β Error:', error.message);
}
})();
β οΈ Security Warning
Never use your API token in client-side JavaScript! It would be exposed to anyone viewing your page source. Always make API calls from your backend server. If you need SMS functionality in a web app, create a backend endpoint that securely calls the BulkSMS API.
// Frontend (JavaScript)
async function sendOTP(phoneNumber) {
try {
const response = await fetch('/api/send-otp', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Use your app's authentication (session, JWT, etc.)
'Authorization': `Bearer ${userAuthToken}`
},
body: JSON.stringify({ phone: phoneNumber })
});
const result = await response.json();
if (result.success) {
console.log('OTP sent successfully!');
} else {
console.error('Failed to send OTP:', result.message);
}
} catch (error) {
console.error('Error:', error);
}
}
// Backend (Node.js/Express) - This is where the API token is used
app.post('/api/send-otp', authenticateUser, async (req, res) => {
const { phone } = req.body;
// Verify user is authenticated
if (!req.user) {
return res.status(401).json({ success: false, message: 'Unauthorized' });
}
const otp = generateOTP(); // Generate 6-digit OTP
try {
// Call BulkSMS API from backend (secure)
const sms = new BulkSMSNigeria(process.env.BULKSMS_API_TOKEN);
await sms.sendSMS('YourApp', `Your OTP is: ${otp}`, phone);
// Store OTP in session/database for verification
req.session.otp = otp;
res.json({ success: true, message: 'OTP sent' });
} catch (error) {
res.status(500).json({ success: false, message: 'Failed to send OTP' });
}
});
Send to multiple recipients in a single API call:
// Recipients as comma-separated string
{
"sender": "YourBrand",
"message": "Flash sale! 30% off all items today only.",
"recipients": "2348012345678,2348087654321,2349012345678"
}
// Or as array (in JSON)
{
"sender": "YourBrand",
"message": "Flash sale! 30% off all items today only.",
"recipients": ["2348012345678", "2348087654321", "2349012345678"]
}
Schedule messages to be sent at a specific time:
{
"sender": "YourBrand",
"message": "Good morning! Your appointment is in 1 hour.",
"recipients": "2348012345678",
"schedule": "2025-12-05 08:00:00" // YYYY-MM-DD HH:MM:SS format
}
Send personalized messages with merge fields:
// Python example
recipients_data = [
{"phone": "2348012345678", "name": "John", "amount": "5000"},
{"phone": "2348087654321", "name": "Sarah", "amount": "3200"},
]
for recipient in recipients_data:
message = f"Hi {recipient['name']}, your order of β¦{recipient['amount']} has been confirmed!"
sms.send_sms('ShopEase', message, recipient['phone'])
// GET request to check delivery status
GET https://www.bulksmsnigeria.com/api/v2/delivery/{message_id}
// Response
{
"status": "success",
"data": {
"message_id": "msg_abc123xyz789",
"total_recipients": 3,
"delivered": 2,
"pending": 1,
"failed": 0,
"recipients": [
{
"phone": "2348012345678",
"status": "delivered",
"delivered_at": "2025-12-04 10:23:15"
},
{
"phone": "2348087654321",
"status": "delivered",
"delivered_at": "2025-12-04 10:23:17"
},
{
"phone": "2349012345678",
"status": "pending",
"reason": "Phone off"
}
]
}
}
// GET request
GET https://www.bulksmsnigeria.com/api/v2/balance
// Response
{
"status": "success",
"data": {
"balance": 1250.50,
"currency": "NGN"
}
}
| Status Code | Meaning | Action |
|---|---|---|
| 200 | Success | Request processed successfully |
| 400 | Bad Request | Check request format, missing/invalid parameters |
| 401 | Unauthorized | Invalid or missing API token |
| 403 | Forbidden | Sender ID not registered or insufficient credits |
| 429 | Too Many Requests | Rate limit exceeded, wait and retry |
| 500 | Server Error | Retry with exponential backoff |
{
"status": "error",
"message": "Insufficient credits. You need β¦60 but have β¦25.",
"error_code": "INSUFFICIENT_CREDITS",
"data": {
"required": 60.00,
"available": 25.00,
"top_up_url": "https://www.bulksmsnigeria.com/wallet/fund"
}
}
import requests
import time
def send_sms_with_retry(sms_client, sender, message, recipients, max_retries=3):
"""
Send SMS with automatic retry on transient failures
"""
for attempt in range(max_retries):
try:
result = sms_client.send_sms(sender, message, recipients)
return result
except requests.exceptions.HTTPError as e:
status_code = e.response.status_code
error_data = e.response.json()
if status_code == 400:
# Bad request - don't retry, fix the request
raise Exception(f"Invalid request: {error_data['message']}")
elif status_code == 401:
# Unauthorized - API token invalid
raise Exception("Authentication failed. Check your API token.")
elif status_code == 403:
# Forbidden - likely insufficient credits or unregistered sender ID
raise Exception(f"Access denied: {error_data['message']}")
elif status_code == 429:
# Rate limit - wait and retry
wait_time = 2 ** attempt # Exponential backoff: 1s, 2s, 4s
print(f"Rate limited. Waiting {wait_time}s before retry...")
time.sleep(wait_time)
continue
elif status_code >= 500:
# Server error - retry with backoff
if attempt < max_retries - 1:
wait_time = 2 ** attempt
print(f"Server error. Retrying in {wait_time}s...")
time.sleep(wait_time)
continue
else:
raise Exception("Server error persists after retries")
else:
raise Exception(f"Unexpected error: {error_data['message']}")
except requests.exceptions.Timeout:
if attempt < max_retries - 1:
print(f"Request timeout. Retrying...")
time.sleep(2)
continue
else:
raise Exception("Request timed out after multiple retries")
except requests.exceptions.ConnectionError:
if attempt < max_retries - 1:
print(f"Connection error. Retrying...")
time.sleep(2)
continue
else:
raise Exception("Could not connect to API server")
raise Exception("Failed after maximum retries")
# Usage
try:
result = send_sms_with_retry(sms, 'YourBrand', 'Test message', '2348012345678')
print("β SMS sent successfully!")
except Exception as e:
print(f"β Failed to send SMS: {e}")
# Log error, send alert, etc.
β DO:
process.env.BULKSMS_API_TOKEN)β DON'T:
// PHP phone number validation
function validateNigerianPhone($phone) {
// Remove spaces, dashes, parentheses
$phone = preg_replace('/[^0-9]/', '', $phone);
// Check format: 234XXXXXXXXXX (13 digits starting with 234)
// or 0XXXXXXXXXX (11 digits starting with 0)
if (preg_match('/^234[789][01]\d{8}$/', $phone)) {
return $phone; // Already in international format
} elseif (preg_match('/^0[789][01]\d{8}$/', $phone)) {
return '234' . substr($phone, 1); // Convert to international
}
return false; // Invalid format
}
// Usage
$phone = validateNigerianPhone('08012345678');
if ($phone) {
// Send SMS to $phone (234801234567)
} else {
// Show error: invalid phone number
}
Respect the API rate limit of 100 requests per minute:
// Python rate limiting example
import time
from collections import deque
class RateLimiter:
def __init__(self, max_calls=100, time_window=60):
self.max_calls = max_calls
self.time_window = time_window
self.calls = deque()
def wait_if_needed(self):
now = time.time()
# Remove old calls outside time window
while self.calls and self.calls[0] < now - self.time_window:
self.calls.popleft()
# If at limit, wait until oldest call expires
if len(self.calls) >= self.max_calls:
sleep_time = self.calls[0] + self.time_window - now
time.sleep(sleep_time)
self.calls.popleft()
# Record this call
self.calls.append(now)
# Usage
limiter = RateLimiter(max_calls=100, time_window=60)
for phone in large_recipient_list:
limiter.wait_if_needed()
sms.send_sms('YourBrand', 'Message', phone)
For large campaigns, use job queues instead of sending synchronously:
// Node.js with Bull Queue
const Queue = require('bull');
const smsQueue = new Queue('sms-sending');
// Producer: Add SMS jobs to queue
async function queueBulkSMS(recipients, message) {
for (const phone of recipients) {
await smsQueue.add({
sender: 'YourBrand',
message: message,
recipient: phone
});
}
}
// Consumer: Process SMS jobs
smsQueue.process(async (job) => {
const { sender, message, recipient } = job.data;
const sms = new BulkSMSNigeria(process.env.BULKSMS_API_TOKEN);
try {
await sms.sendSMS(sender, message, recipient);
return { success: true };
} catch (error) {
// Will retry automatically based on queue settings
throw error;
}
});
// Python logging example
import logging
logging.basicConfig(
filename='sms_api.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def send_sms_with_logging(sms_client, sender, message, recipients):
logging.info(f"Sending SMS to {len(recipients.split(','))} recipients")
try:
result = sms_client.send_sms(sender, message, recipients)
logging.info(f"SMS sent successfully. Message ID: {result['data']['message_id']}")
logging.info(f"Cost: β¦{result['data']['total_cost']}, Balance: β¦{result['data']['balance_after']}")
return result
except Exception as e:
logging.error(f"SMS sending failed: {str(e)}")
raise
Check balance before sending to avoid failures:
// Check balance before campaign
const balance = await sms.getBalance();
const estimatedCost = recipientCount * 6; // Assume β¦6 per SMS
if (balance.data.balance < estimatedCost) {
throw new Error(
`Insufficient balance. Need β¦${estimatedCost} but have β¦${balance.data.balance}`
);
}
// Proceed with sending
await sms.sendSMS('YourBrand', message, recipients);
Error:
SSL: CERTIFICATE_VERIFY_FAILED
Solution:
Update your system's CA certificates or ensure your HTTP library is using updated certificates. Never disable SSL verification in production!
Solution:
Causes:
Solution:
Register your sender ID through your dashboard and wait for approval (3-7 days) before using in campaigns.
Congratulations! You now have a fully functional SMS API integration. Here's what to do next:
Get your API credentials, 50 free SMS credits, and start sending in minutes. Complete documentation, code examples, and 24/7 developer support included.
Start sending bulk SMS to your customers today with 50 free SMS credits.
Get Started Free