eHub SMS REST API
Send SMS, manage sender IDs, and check your wallet programmatically. All endpoints require HMAC-signed requests.
Overview
Everything you need to integrate eHub SMS into your application.
The eHub SMS API is a REST API that accepts JSON request bodies and returns JSON responses. Every request must be authenticated with a Bearer token and signed with an HMAC-SHA256 signature to prevent replay attacks.
Authentication
Every request requires your API key in the Authorization header.
Authorization: Bearer YOUR_API_KEY
Your API key identifies your account. Keep it secret — never expose it in client-side code or public repositories. All requests must also include a valid HMAC signature (see below).
Request Signing
Every request must be signed with HMAC-SHA256 using your API secret.
401 Unauthorized.Signing algorithm
# Concatenate these four values with newlines {unix_timestamp} {HTTP_METHOD} {/full/path} {raw_body} # Signature signature = HMAC-SHA256(secret, payload) # hex-encoded
Required headers
| Header | Value | Notes |
|---|---|---|
X-Timestamp | Unix timestamp (integer) | Must be within ±5 min of server time |
X-Signature | HMAC-SHA256 hex string | Signed with your API secret |
"" as the body. Timestamps use Unix time (seconds since epoch) — timezone doesn't matter.Code examples
// PHP signing example $apiKey = 'sk_your_api_key'; $secret = 'your_api_secret'; $method = 'POST'; $path = '/api/v1/sms/send'; $body = json_encode(['to' => '255755000000', 'message' => 'Hello!', 'sender_id' => 'uuid-here']); $timestamp = time(); $payload = $timestamp . "\n" . $method . "\n" . $path . "\n" . $body; $signature = hash_hmac('sha256', $payload, $secret); $response = Http::withHeaders([ 'Authorization' => 'Bearer ' . $apiKey, 'X-Timestamp' => $timestamp, 'X-Signature' => $signature, 'Content-Type' => 'application/json', ])->post('https://sms.ehub.co.tz/api/v1/sms/send', json_decode($body, true));
# Python signing example import hmac, hashlib, time, json, requests api_key = 'sk_your_api_key' secret = 'your_api_secret' method = 'POST' path = '/api/v1/sms/send' body = json.dumps({'to': '255755000000', 'message': 'Hello!', 'sender_id': 'uuid-here'}) timestamp = str(int(time.time())) payload = f"{timestamp}\n{method}\n{path}\n{body}" signature = hmac.new(secret.encode(), payload.encode(), hashlib.sha256).hexdigest() response = requests.post( 'https://sms.ehub.co.tz/api/v1/sms/send', headers={ 'Authorization': f'Bearer {api_key}', 'X-Timestamp': timestamp, 'X-Signature': signature, 'Content-Type': 'application/json', }, data=body )
// Go signing example package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "net/http" "strings" "strconv" "time" ) func sign(secret, method, path, body string) (string, string) { ts := strconv.FormatInt(time.Now().Unix(), 10) payload := ts + "\n" + method + "\n" + path + "\n" + body mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(payload)) return ts, hex.EncodeToString(mac.Sum(nil)) } func main() { apiKey := "sk_your_api_key" secret := "your_api_secret" body := `{"to":"255755000000","message":"Hello!","sender_id":"uuid-here"}` ts, sig := sign(secret, "POST", "/api/v1/sms/send", body) req, _ := http.NewRequest("POST", "https://sms.ehub.co.tz/api/v1/sms/send", strings.NewReader(body)) req.Header.Set("Authorization", "Bearer "+apiKey) req.Header.Set("X-Timestamp", ts) req.Header.Set("X-Signature", sig) req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) fmt.Println(resp.Status, err) }
// Java signing example import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.net.URI; import java.net.http.*; import java.time.Instant; public class EhubSmsClient { static String sign(String secret, String method, String path, String body) throws Exception { String ts = String.valueOf(Instant.now().getEpochSecond()); String payload = ts + "\n" + method + "\n" + path + "\n" + body; Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256")); byte[] raw = mac.doFinal(payload.getBytes()); StringBuilder hex = new StringBuilder(); for (byte b : raw) hex.append(String.format("%02x", b)); return ts + ":" + hex; // returns "timestamp:signature" } public static void main(String[] args) throws Exception { String apiKey = "sk_your_api_key"; String secret = "your_api_secret"; String body = "{\"to\":\"255755000000\",\"message\":\"Hello!\",\"sender_id\":\"uuid-here\"}"; String[] parts = sign(secret, "POST", "/api/v1/sms/send", body).split(":", 2); String ts = parts[0]; String sig = parts[1]; HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://sms.ehub.co.tz/api/v1/sms/send")) .header("Authorization", "Bearer " + apiKey) .header("X-Timestamp", ts) .header("X-Signature", sig) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(resp.body()); } }
# cURL signing example (bash) API_KEY="sk_your_api_key" SECRET="your_api_secret" BODY='{"to":"255755000000","message":"Hello!","sender_id":"uuid-here"}' TS=$(date +%s) PAYLOAD="${TS} POST /api/v1/sms/send ${BODY}" SIG=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}') curl -s -X POST https://sms.ehub.co.tz/api/v1/sms/send \ -H "Authorization: Bearer $API_KEY" \ -H "X-Timestamp: $TS" \ -H "X-Signature: $SIG" \ -H "Content-Type: application/json" \ -d "$BODY"
Rate Limiting
120 requests per minute per API key.
Every response includes rate limit headers so you can track your usage in real time. When the limit is exceeded, the API returns 429 Too Many Requests with a retry_after field.
| Header | Description |
|---|---|
X-RateLimit-Limit | Max requests per minute for your key |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
Response Format
All responses are JSON with a consistent envelope.
{
"success": true,
"message": "SMS sent successfully",
"data": { /* endpoint-specific payload */ },
"timestamp": "2026-06-05T10:00:00+03:00"
}{
"success": false,
"message": "Invalid sender_id.",
"timestamp": "2026-06-05T10:00:00+03:00"
}Error Codes
Standard HTTP status codes with descriptive messages.
| Status | Meaning |
|---|---|
| 401 | Missing/invalid API key · Expired timestamp · Invalid HMAC signature · Inactive account |
| 403 | Sender ID not found or not accessible · Sender ID not yet approved |
| 409 | Duplicate sender ID — you have already registered this name |
| 422 | Validation failed — check the errors field for details |
| 429 | Rate limit exceeded — wait until the window resets (retry_after seconds) |
| 500 | Internal server error — contact support if this persists |
Sender IDs
Manage the alphanumeric names that appear as the SMS sender.
id (UUID) field when sending SMS.
GET /api/v1/sender-ids Authorization: Bearer sk_... X-Timestamp: 1780658993 X-Signature: abc123...
{
"success": true,
"data": {
"own": [
{
"id": "00420892-38bd-47b0-9a5f-ea55bef5d2d1",
"sender_name": "MICHANGO",
"status": "approved",
"purpose": "TRANSACTIONAL",
"is_default": false,
"type": "own",
"created_at": "2026-01-27T14:40:26+03:00"
}
],
"shared": [],
"public": []
}
}| Field | Type | Description | |
|---|---|---|---|
sender_id | string | required | 4–11 alphanumeric characters, will be uppercased |
purpose | string | optional | Intended use, e.g. "Marketing promotions" |
POST /api/v1/sender-ids Authorization: Bearer sk_... X-Timestamp: 1780658993 X-Signature: abc123... Content-Type: application/json { "sender_id": "MYBRAND", "purpose": "Promotional messages" }
{
"success": true,
"message": "Sender ID submitted for approval.",
"data": {
"id": "e47c45f3-43ce-4bcc-b868-9db92840f11f",
"sender_name": "MYBRAND",
"status": "pending",
"purpose": "Promotional messages",
"created_at": "2026-06-05T14:33:52+03:00"
}
}SMS
Send single or bulk SMS messages to Tanzanian phone numbers.
| Field | Type | Description | |
|---|---|---|---|
to | string | required | Recipient phone number (e.g. 255755000000 or 0755000000) |
message | string | required | SMS body text, max 640 characters (4 SMS parts) |
sender_id | uuid | required | UUID of an approved sender ID from GET /sender-ids |
POST /api/v1/sms/send Authorization: Bearer sk_... X-Timestamp: 1780658993 X-Signature: abc123... Content-Type: application/json { "to": "255755957514", "message": "Your verification code is 123456", "sender_id": "00420892-38bd-47b0-9a5f-ea55bef5d2d1" }
{
"success": true,
"message": "SMS sent successfully",
"data": {
"message_id": "2d853818-5529-4838-bee0-1cb012b1a136",
"to": "255755957514",
"status": "sent",
"cost": "25.00",
"parts": 1,
"created_at": "2026-06-05T13:45:33+03:00"
}
}| Field | Type | Description | |
|---|---|---|---|
recipients | array | required | Array of phone numbers, max 1000. Duplicates are removed. |
message | string | required | SMS body text, max 640 characters |
sender_id | uuid | required | UUID of an approved sender ID |
campaign_name | string | optional | Label for this campaign, max 255 characters |
scheduled_at | datetime | optional | ISO 8601 future datetime to schedule the send |
POST /api/v1/sms/send-bulk Content-Type: application/json { "recipients": ["255755000001", "255755000002", "255755000003"], "message": "Flash sale! 30% off today only.", "sender_id": "00420892-38bd-47b0-9a5f-ea55bef5d2d1", "campaign_name": "June Flash Sale", "scheduled_at": null }
{
"success": true,
"message": "Bulk SMS campaign created successfully",
"data": {
"campaign_id": "f84e8878-27be-4bce-aac8-98e1b3ec19a0",
"name": "June Flash Sale",
"total_recipients": 3,
"total_cost": "75.00",
"status": "processing",
"scheduled_at": null,
"created_at": "2026-06-05T13:45:57+03:00"
}
}| Parameter | Type | Description | |
|---|---|---|---|
status | string | optional | pending · queued · sent · delivered · failed |
from_date | date | optional | Filter from date (YYYY-MM-DD) |
to_date | date | optional | Filter to date (YYYY-MM-DD) |
limit | integer | optional | Results per page, max 100. Default: 20 |
page | integer | optional | Page number. Default: 1 |
GET /api/v1/sms/history?status=delivered&limit=20&page=1 Authorization: Bearer sk_... X-Timestamp: 1780658993 X-Signature: abc123...
{
"success": true,
"data": {
"messages": [
{
"message_id": "2d853818-5529-4838-bee0-1cb012b1a136",
"to": "255755957514",
"message": "Your code is 123456",
"sender_id": "MICHANGO",
"status": "delivered",
"cost": "25.00",
"parts": 1,
"sent_at": "2026-06-05T13:45:35+03:00",
"delivered_at": "2026-06-05T13:45:37+03:00",
"created_at": "2026-06-05T13:45:33+03:00"
}
],
"pagination": {
"current_page": 1,
"per_page": 20,
"total": 142,
"last_page": 8
}
}
}GET /api/v1/sms/2d853818-5529-4838-bee0-1cb012b1a136
{
"success": true,
"data": {
"message_id": "2d853818-5529-4838-bee0-1cb012b1a136",
"to": "255755957514",
"status": "delivered",
"cost": "25.00",
"sent_at": "2026-06-05T13:45:35+03:00",
"delivered_at": "2026-06-05T13:45:37+03:00",
"error_message": null,
"created_at": "2026-06-05T13:45:33+03:00"
}
}Wallet
Check your SMS balance and transaction history.
GET /api/v1/wallet/balance
{
"success": true,
"data": {
"balance": "0.00",
"sms_balance": 78,
"currency": "TZS",
"updated_at": "2026-06-05T13:45:16+03:00"
}
}| Parameter | Type | Description | |
|---|---|---|---|
limit | integer | optional | Results per page, max 100. Default: 20 |
page | integer | optional | Page number. Default: 1 |
GET /api/v1/wallet/transactions?limit=20&page=1 Authorization: Bearer sk_... X-Timestamp: 1780658993 X-Signature: abc123...
{
"success": true,
"data": {
"transactions": [
{
"id": "txn_9c3a1d88-f401-4b2a-91c3-d7ec1e028f3b",
"type": "debit",
"amount": "25.00",
"description": "SMS to 255755957514",
"balance": "1950.00",
"created_at": "2026-06-05T13:45:33+03:00"
},
{
"id": "txn_2aa7e1b3-0c43-4d19-b98c-0fa3c9b23ef0",
"type": "credit",
"amount": "5000.00",
"description": "Wallet top-up via Snippe",
"balance": "1975.00",
"created_at": "2026-06-04T09:12:05+03:00"
}
],
"pagination": {
"current_page": 1,
"per_page": 20,
"total": 47,
"last_page": 3
}
}
}