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_at is 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:

Production
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.

POST /sms/send

Request Headers

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.

201 Created — Message submitted
{
  "status_code":       201,
  "success":           true,
  "message_batch_id":  "abc123xyz",
  "message":           "Message submitted successfully."
}
Error responses
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:

JSON
"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.

GET /balance/
200 OK
{
  "balance": 4250.0
}
cURL example
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.

GET /sms/delivery-reports/{batch_id}
200 OK
{
  "message_batch_id": "abc123xyz",
  "summary": {
    "total":     3,
    "delivered": 2,
    "failed":    1
  },
  "messages": [
    {
      "to_number":    "+255712345678",
      "status":       "delivered",
      "delivered_at": "2024-01-15T10:30:00Z"
    },
    ...
  ]
}
404 Not Found — report not yet available
{ "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.

  1. Log in to the portal and go to Settings → Webhooks.
  2. Click Add Webhook and enter your endpoint URL.
  3. Copy the webhook secret shown — it is only displayed once. You will need it to verify incoming requests.
  4. 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:

POST your-webhook-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 200 within 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.

  1. Read the raw request body (before JSON parsing).
  2. Compute HMAC-SHA256 of the raw body using your webhook secret as the key.
  3. Compare the computed hash (hex-encoded) with the value in the X-Wasambazie-Signature header.
  4. If they do not match, reject the request (return 400).
Python — signature verification
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

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.

Standard error format
{ "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

422 example
{
  "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.