Developers
SMS API in Uganda: A Developer's Guide to Sending Your First Message
Send your first SMS from a Ugandan SMS API in under 5 minutes. cURL, Node, PHP and Python examples. HTTP Basic Auth, JSON, MTN + Airtel routing.
If you're building a fintech, SaaS, marketplace, or appointment system for the Ugandan market, you need a programmatic way to send SMS. Here's the developer's view of the Wesendall API — designed for someone who wants to ship in an afternoon, not read 80 pages of documentation.
Architecture in 60 seconds
- Auth: HTTP Basic Auth. API key as username, API secret as password.
- Base URL:
https://www.wesendall.com/api/v1 - Content type: JSON in, JSON out.
- Scoping: every operation requires a
walletId(your wallet's stable ID) to confirm the API key is authorized for that wallet. - Billing: UGX-denominated wallet. Charges atomic + refundable on failure.
- Top-ups: Mobile Money via API or dashboard.
That's the whole model.
Step 1 — Create an API key
Sign up at wesendall.com/register. After verifying your phone, top up with at least UGX 500 of Mobile Money so you can actually send a message.
Navigate to Settings & Advanced → API in the sidebar. Click Create API key, name it (e.g. "Production backend"), and copy the credentials:
keyPrefix(e.g.sk_live_abc123…) — public, used as the Basic Auth usernamesecret— secret, used as the Basic Auth password (shown once, then hashed)walletId— your wallet's stable ID, used in every request
Stash the secret in your secrets manager. You can mint additional keys per environment or revoke any of them in one click.
Step 2 — Check your wallet balance
curl -X GET "https://www.wesendall.com/api/v1/account/balance?walletId=WALLET_ID" \
-u "API_KEY:API_SECRET"
Response:
{
"success": true,
"data": {
"wallet_id": "WALLET_ID",
"balance": 5000,
"currency": "UGX",
"cost_per_sms": 35
}
}
Step 3 — Send a single SMS
curl -X POST "https://www.wesendall.com/api/v1/sms/send" \
-u "API_KEY:API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"walletId": "WALLET_ID",
"message": "Hello from Wesendall",
"recipient": "+256700000000"
}'
Response on success:
{
"success": true,
"message": "SMS sent to 1/1 recipient(s)",
"data": {
"wallet_id": "WALLET_ID",
"total_recipients": 1,
"successful": 1,
"failed": 0,
"sms_units": 1,
"total_cost": 35,
"currency": "UGX",
"remaining_balance": 4965,
"results": [
{ "recipient": "+256700000000", "status": "sent", "transaction_id": "..." }
]
}
}
Step 4 — Send bulk SMS
The same endpoint handles bulk — recipient accepts a single phone, an array,
or a comma/space separated string.
curl -X POST "https://www.wesendall.com/api/v1/sms/send" \
-u "API_KEY:API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"walletId": "WALLET_ID",
"message": "Your appointment is at 10 AM tomorrow",
"recipient": ["+256700000000", "+256701234567", "+256702345678"]
}'
The wallet is charged the total upfront, atomically. If the provider rejects the send, the full amount is refunded inside the same request.
Step 5 — Send to a saved group
# 1. Create a group
curl -X POST "https://www.wesendall.com/api/v1/groups" \
-u "API_KEY:API_SECRET" \
-H "Content-Type: application/json" \
-d '{ "walletId": "WALLET_ID", "title": "Q2 Promo" }'
# 2. Add contacts
curl -X POST "https://www.wesendall.com/api/v1/groups/GROUP_ID/contacts" \
-u "API_KEY:API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"walletId": "WALLET_ID",
"contacts": [
{ "phone": "+256700000000", "name": "Sarah" },
{ "phone": "+256701234567" }
]
}'
# 3. Send to the whole group
curl -X POST "https://www.wesendall.com/api/v1/sms/group" \
-u "API_KEY:API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"walletId": "WALLET_ID",
"groupId": "GROUP_ID",
"message": "Q2 promo: 20% off this Friday"
}'
OTPs and verification flows
OTP delivery on MTN Uganda is typically 3–5 seconds end-to-end, 4–7 seconds on
Airtel. You generate the code (it never leaves your control), put it in the
message field, and ship.
// Node.js example
async function sendOtp(phone, code) {
const credentials = Buffer.from(
`${process.env.WESENDALL_KEY}:${process.env.WESENDALL_SECRET}`
).toString("base64");
const response = await fetch("https://www.wesendall.com/api/v1/sms/send", {
method: "POST",
headers: {
Authorization: `Basic ${credentials}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
walletId: process.env.WESENDALL_WALLET_ID,
message: `Your verification code is ${code}. Valid for 10 minutes.`,
recipient: phone,
}),
});
if (!response.ok) {
throw new Error(`OTP send failed: ${response.status}`);
}
return response.json();
}
Top-ups via API
Automate wallet top-ups so you never run out at 2 AM:
curl -X POST "https://www.wesendall.com/api/v1/account/topup" \
-u "API_KEY:API_SECRET" \
-H "Content-Type: application/json" \
-d '{
"walletId": "WALLET_ID",
"amount": 10000,
"phone_number": "+256700000000"
}'
This initiates a Mobile Money charge against the supplied phone number. The wallet credits within seconds of the user approving on their phone.
Error handling — what to actually retry
- 401
invalid_credentials— bad API key/secret. Don't retry. - 402
insufficient_balance— top up the wallet, then retry. - 422
validation_failed— fix the request, then retry. - 502
send_failed— transient provider error. Retry once with backoff. The wallet has already been refunded. - 500
internal_error— transient. Retry with backoff.
SMS history for auditing
curl -X GET "https://www.wesendall.com/api/v1/sms/history?walletId=WALLET_ID&page=1&per_page=50" \
-u "API_KEY:API_SECRET"
Paginated history per wallet. Use it for compliance, finance reconciliation, and debugging "did that message actually go?" questions.
Going to production
- Use separate API keys per environment (staging, prod).
- Set per-environment Wallet IDs if you want stricter isolation — create a second account for staging.
- Watch the lastUsedAt field in the dashboard to spot unused or compromised keys.
- For high-volume OTP traffic, talk to us about dedicated routing.
Ship today
Create your account, mint a key in the API page, paste the cURL. Your next message is ready.