API Documentation
Everything you need to integrate Wasambazie SMS into your application.
Overview
Wasambazie provides a REST API for programmatic SMS sending. All requests and responses use JSON. Two authentication schemes are supported:
- API Key — for all SMS, webhook, and balance operations. Recommended.
- JWT — for portal / dashboard operations (not covered in this guide).
This guide focuses exclusively on the API Key flow — what you need to integrate SMS into your application.
You need an active Wasambazie account and API keys to use this API. Sign up for free to get started.
Authentication
Every API request must include two headers: a public key and a secret key. Obtain them from your portal under Settings → API Keys → Generate New Key Pair.
| Header | Description |
|---|---|
| X-API-PUBLIC-KEY | Your public API key. Always prefixed with pk_. |
| X-API-SECRET-KEY | Your secret API key. Always prefixed with sk_. Shown only once at generation — store it securely immediately. It cannot be retrieved again, only regenerated. |
Key Rules
- Keys are tied to your account. Never share or expose them in client-side code (browsers, mobile apps).
- A key can be revoked at any time from the portal.
last_used_atis recorded on every authenticated request.
If a request is made with missing or invalid keys the API returns HTTP 401 Unauthorized with {"detail": "Invalid or missing API credentials"}.
Base URL
All API endpoints are relative to the following base URL:
https://wasambazie.co.tz/v1
All paths in this guide are relative to /v1. All requests must use HTTPS.
Send SMS
Send one or more SMS messages. A batch is created even for a single recipient, and you receive a message_batch_id for tracking delivery.
Request Headers
Content-Type: application/json X-API-PUBLIC-KEY: pk_... X-API-SECRET-KEY: sk_...
Request Body
| Field | Type | Description |
|---|---|---|
| to_number required | string | Single number or comma-separated list. Use international format: +255XXXXXXXXX. |
| message required | string | Plain text content of the SMS. See Segmentation & Billing for length limits. |
| sender_id required | string | The exact approved sender name (e.g. MYBRAND). Must be approved in your account before use. See Sender IDs. |
Success Response — 201 Created
Save message_batch_id — you need it to poll delivery reports or match incoming webhook payloads.
{
"status_code": 201,
"success": true,
"message_batch_id": "abc123xyz",
"message": "Message submitted successfully."
}
400 { "detail": "Sender ID is required" }
402 { "detail": "Insufficient SMS credits" }
404 { "detail": "Sender ID not found or not approved" }
502 { "detail": "Provider rejected the message" } // no credits deducted
Credits are deducted after the provider accepts the message. If the provider returns a 502 error, no credits are charged. Sending to N recipients costs: segments(message) × N credits.
Sender IDs
A Sender ID is the name that appears in place of a phone number on the recipient's device (e.g. MYBRAND instead of +255...). All Sender IDs must be approved by the Wasambazie team before they can be used in API calls.
Rules
- Maximum 11 characters.
- Letters and numbers only — no spaces, no symbols.
- All uppercase (the API normalises to uppercase automatically).
- Must be approved before use — requests submitted through the portal.
Requesting a Sender ID
Go to Settings → Sender IDs in the portal and submit your request. Approval typically takes 1–2 weeks. You will receive a status update in the portal once processed.
| Status | Description |
|---|---|
| pending | Submitted, awaiting review. |
| approved | Active — can be used in POST /sms/send. |
| rejected | Not approved. Submit a new request with a different name. |
Using a Sender ID in API Calls
Pass the sender name string (not a UUID) in the sender_id field of your send request:
"sender_id": "MYBRAND"
Check Balance
Returns your current SMS credit balance. The value is a count of SMS credits — each credit equals 1 SMS segment sent to 1 recipient. It is not a monetary value.
{
"balance": 4250.0
}
curl -X GET 'https://wasambazie.co.tz/v1/balance/' \ -H 'X-API-PUBLIC-KEY: pk_your-public-key' \ -H 'X-API-SECRET-KEY: sk_your-secret-key'
Check your balance before bulk sends to avoid partial delivery due to insufficient credits.
Delivery Reports (Polling)
If you prefer polling over webhooks, query the delivery report for a batch after sending using the message_batch_id returned by POST /sms/send.
{
"message_batch_id": "abc123xyz",
"summary": {
"total": 3,
"delivered": 2,
"failed": 1
},
"messages": [
{
"to_number": "+255712345678",
"status": "delivered",
"delivered_at": "2024-01-15T10:30:00Z"
},
...
]
}
{ "detail": "Report not yet available — the batch is still being processed." }
Polling Guidance
- Wait at least 30 seconds before the first poll.
- Use exponential backoff: 30s → 60s → 120s → 240s…
- Delivery is not instantaneous; network and operator delays apply.
- For high-volume use, Webhooks are strongly preferred over polling.
Webhooks
Webhooks allow Wasambazie to POST delivery report data to your server automatically when a batch is finalised — no polling required.
Setting Up a Webhook
Webhooks are managed entirely through the portal. No API calls are needed to create or configure them.
- Log in to the portal and go to Settings → Webhooks.
- Click Add Webhook and enter your endpoint URL.
- Copy the webhook secret shown — it is only displayed once. You will need it to verify incoming requests.
- Your webhook is active immediately.
From the portal you can also view all registered webhooks, enable/disable them, inspect delivery attempt history, and manually retry failed deliveries.
Webhook Payload
When a delivery batch is finalised, Wasambazie sends a POST request to your registered URL:
{
"event": "delivery.batch.finalized",
"message_batch_id": "abc123xyz",
"summary": {
"total": 3,
"delivered": 2,
"failed": 1
},
"messages": [
{
"to_number": "+255712345678",
"status": "delivered",
"delivered_at": "2024-01-15T10:30:00Z"
},
...
]
}
Responding to a Webhook
- Your endpoint must respond with
HTTP 200within a reasonable timeout. - Any non-200 response (or connection error / timeout) is treated as a failure and will be retried.
- Retry schedule: up to 5 attempts with exponential backoff.
Signature Verification (Recommended)
Use your webhook secret to verify that incoming requests genuinely come from Wasambazie and have not been tampered with.
- Read the raw request body (before JSON parsing).
- Compute HMAC-SHA256 of the raw body using your webhook secret as the key.
- Compare the computed hash (hex-encoded) with the value in the
X-Wasambazie-Signatureheader. - If they do not match, reject the request (return 400).
import hmac, hashlib
def verify(secret: str, payload: bytes, signature: str) -> bool:
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Message Status Values
These status values are returned in delivery report responses and webhook payloads. Use case-insensitive comparison in your code for robustness.
| Status | Description |
|---|---|
| queued | Message accepted by the gateway, waiting to be sent to the provider. |
| sent | Message has been handed to the SMS provider. |
| delivered | Provider confirmed delivery to the handset. |
| failed | Delivery failed — provider reported an error or a timeout occurred. |
Segmentation & Billing
SMS messages are split into segments for billing purposes. Each credit equals 1 segment sent to 1 recipient.
| Encoding | Single part | Multi-part (per segment) |
|---|---|---|
| GSM-7 (standard Latin) | 160 characters | 153 characters |
| Unicode (Arabic, Chinese, emoji, etc.) | 70 characters | 67 characters |
Credit Cost Formula
credits_used = num_segments(message) × num_recipients
Examples
| Message length | Segments | Recipients | Credits used |
|---|---|---|---|
| 120 characters | 1 | 100 | 100 |
| 180 characters | 2 | 50 | 100 |
Error Reference
All errors return JSON with a detail field containing a human-readable message.
{ "detail": "Human-readable error message" }
| HTTP Status | Meaning |
|---|---|
| 200 | OK — request succeeded. |
| 201 | Created — resource created successfully. |
| 204 | No Content — deletion succeeded. |
| 400 | Bad Request — missing or invalid fields. Read detail for the reason. |
| 401 | Unauthorized — missing, invalid, or revoked API keys. |
| 402 | Payment Required — not enough SMS credits. |
| 403 | Forbidden — you do not have permission for this resource. |
| 404 | Not Found — resource does not exist or does not belong to your account. |
| 409 | Conflict — duplicate entry (e.g. phone number already registered). |
| 422 | Unprocessable Entity — field-level validation errors. Body contains a detail array with per-field messages (see below). |
| 500 | Internal Server Error — unexpected error on our side. Retry the request. |
| 502 | Bad Gateway — upstream SMS provider error. No credits are deducted. |
422 Validation Error Format
{
"detail": [
{
"loc": ["body", "to_number"],
"msg": "field required",
"type": "value_error.missing"
}
]
}
Code Examples
Full working examples for the most common operations.
# Check balance
curl -X GET 'https://wasambazie.co.tz/v1/balance/' \
-H 'X-API-PUBLIC-KEY: pk_your-public-key' \
-H 'X-API-SECRET-KEY: sk_your-secret-key'
# Send a single SMS
curl -X POST 'https://wasambazie.co.tz/v1/sms/send' \
-H 'Content-Type: application/json' \
-H 'X-API-PUBLIC-KEY: pk_your-public-key' \
-H 'X-API-SECRET-KEY: sk_your-secret-key' \
-d '{
"to_number": "+255712345678",
"message": "Hello from Wasambazie!",
"sender_id": "MYBRAND"
}'
# Send to multiple recipients (comma-separated)
curl -X POST 'https://wasambazie.co.tz/v1/sms/send' \
-H 'Content-Type: application/json' \
-H 'X-API-PUBLIC-KEY: pk_your-public-key' \
-H 'X-API-SECRET-KEY: sk_your-secret-key' \
-d '{
"to_number": "+255712345678,+255722345678,+255732345679",
"message": "Your order has been confirmed.",
"sender_id": "MYBRAND"
}'
# Poll delivery report
curl -X GET 'https://wasambazie.co.tz/v1/sms/delivery-reports/abc123xyz' \
-H 'X-API-PUBLIC-KEY: pk_your-public-key' \
-H 'X-API-SECRET-KEY: sk_your-secret-key'
const axios = require('axios');
const headers = {
'Content-Type': 'application/json',
'X-API-PUBLIC-KEY': 'pk_your-public-key',
'X-API-SECRET-KEY': 'sk_your-secret-key',
};
const BASE = 'https://wasambazie.co.tz/v1';
// Check balance
async function getBalance() {
const { data } = await axios.get(`${BASE}/balance/`, { headers });
console.log('Balance:', data.balance);
}
// Send SMS
async function sendSMS(toNumber, message, senderId) {
const { data } = await axios.post(`${BASE}/sms/send`, {
to_number: toNumber,
message: message,
sender_id: senderId,
}, { headers });
console.log('Batch ID:', data.message_batch_id);
return data.message_batch_id;
}
// Poll delivery report
async function getReport(batchId) {
const { data } = await axios.get(
`${BASE}/sms/delivery-reports/${batchId}`,
{ headers }
);
console.log('Report:', data);
}
// Example usage
(async () => {
await getBalance();
const batchId = await sendSMS('+255712345678', 'Hello!', 'MYBRAND');
setTimeout(() => getReport(batchId), 30000); // poll after 30s
})();
import requests
import time
BASE = 'https://wasambazie.co.tz/v1'
HEADERS = {
'Content-Type': 'application/json',
'X-API-PUBLIC-KEY': 'pk_your-public-key',
'X-API-SECRET-KEY': 'sk_your-secret-key',
}
# Check balance
def get_balance():
r = requests.get(f'{BASE}/balance/', headers=HEADERS)
r.raise_for_status()
print('Balance:', r.json()['balance'])
# Send SMS
def send_sms(to_number, message, sender_id):
r = requests.post(f'{BASE}/sms/send', headers=HEADERS, json={
'to_number': to_number,
'message': message,
'sender_id': sender_id,
})
r.raise_for_status()
batch_id = r.json()['message_batch_id']
print('Batch ID:', batch_id)
return batch_id
# Poll delivery report
def get_report(batch_id):
r = requests.get(f'{BASE}/sms/delivery-reports/{batch_id}', headers=HEADERS)
r.raise_for_status()
print('Report:', r.json())
# Example usage
get_balance()
batch_id = send_sms('+255712345678', 'Hello from Wasambazie!', 'MYBRAND')
time.sleep(30) # wait before polling
get_report(batch_id)
<?php
define('BASE', 'https://wasambazie.co.tz/v1');
$headers = [
'Content-Type: application/json',
'X-API-PUBLIC-KEY: pk_your-public-key',
'X-API-SECRET-KEY: sk_your-secret-key',
];
function apiRequest(string $method, string $path, ?array $body = null): array {
global $headers;
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
]);
if ($body) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Check balance
$balance = apiRequest('GET', '/balance/');
echo 'Balance: ' . $balance['balance'] . PHP_EOL;
// Send SMS
$result = apiRequest('POST', '/sms/send', [
'to_number' => '+255712345678',
'message' => 'Hello from Wasambazie!',
'sender_id' => 'MYBRAND',
]);
echo 'Batch ID: ' . $result['message_batch_id'] . PHP_EOL;
// Poll delivery report
sleep(30);
$report = apiRequest('GET', '/sms/delivery-reports/' . $result['message_batch_id']);
print_r($report);
Replace pk_your-public-key and sk_your-secret-key with your real API keys from the portal dashboard. Your secret key is shown only once at generation.