Ruka hadi maudhui makuu

Tafsiri inakuja — ukurasa huu unaonyeshwa kwa Kiingereza wakati tafsiri ya Kiswahili inaandaliwa. Soma kwa Kiingereza

SMS API

SMS API for Tanzania: send with one HTTPS request

A versioned REST API with scoped keys, idempotency, signed webhooks and delivery status for every message. Create a key in your dashboard and send your first SMS in minutes.

Base URL
https://api.lundosms.co.tz/v1
Authentication
Authorization: Bearer YOUR_API_KEY
Format
JSON over HTTPS
Per request
Up to 1,000 recipients

Quick start

Four steps from nothing to a delivered message.

  1. 1

    Create an API key

    In your dashboard, open the Developer section and create a key. Start with a test key (lsk_test_…); live keys start with lsk_live_…. Copy the key when it is shown — it appears only once.

  2. 2

    Have a sender ID approved

    Messages are sent with an approved sender ID. Request yours from the dashboard; the API returns sender_id_not_approved until it is approved.

  3. 3

    Send a message

    POST to /v1/messages with your sender ID, the recipients and the text. Numbers can be written as 0712 345 678, 255712345678 or +255712345678 — they come back in international format.

    POST /v1/messages

    curl -X POST 'https://api.lundosms.co.tz/v1/messages' \
      -H 'Authorization: Bearer YOUR_API_KEY' \
      -H 'Idempotency-Key: order-1001' \
      -H 'Content-Type: application/json' \
      -d '{
        "sender_id": "YOURBRAND",
        "recipients": ["255712345678"],
        "message": "Your order has been confirmed.",
        "client_reference": "ORDER-1001",
        "type": "transactional"
      }'
  4. 4

    Read the response

    A 202 means the messages are accepted and their credits reserved. Each message has its own id: fetch its status from GET /v1/messages/<id>, or let a webhook tell you when it changes.

What you get back

The 202 response lists every accepted message with its id and status. Recipients that cannot be accepted — an invalid number, an opted-out number, a duplicate — are returned in rejected_recipients with a reason, while the valid ones go ahead. If none is valid, the request fails with 422 no_valid_recipients; if there are not enough credits, with 402 insufficient_credits and nothing is sent.

202 Accepted
{
  "success": true,
  "code": "messages_accepted",
  "message": "1 message accepted.",
  "data": {
    "batch_id": "0192c0d1-7c3e-7a52-9f31-2b6c8a1d4e90",
    "status": "queued",
    "accepted": 1,
    "rejected": 0,
    "encoding": "gsm7",
    "segments_per_message": 1,
    "credits_reserved": 1,
    "messages": [
      {
        "id": "0192c0d1-7c3e-7a52-9f31-2b6c8a1d4e91",
        "to": "+255712345678",
        "status": "queued",
        "client_reference": "ORDER-1001"
      }
    ],
    "rejected_recipients": []
  },
  "request_id": "0192c0d1-7c3e-7a52-9f31-2b6c8a1d4e92"
}

Endpoints

The customer REST API
EndpointWhat it doesScope
POST /v1/messagesSend one or many messagesmessages:send
POST /v1/messages/estimateEncoding, segments and credits for a textmessages:send
GET /v1/messages/<id>Status, segments and timestamps of one messagemessages:read
GET /v1/messagesList and filter message history (cursor paging)messages:read
GET /v1/balanceAvailable and reserved creditsbalance:read
GET /v1/sender-idsYour approved sender IDssender_ids:read
GET · POST · DELETE /v1/webhooksCreate, list and delete webhookswebhooks:manage
GET /v1/openapi.yamlThe OpenAPI 3.1 contractpublic

Message statuses

"Submitted" means the operator network accepted the message. It is never reported as delivered until the handset confirms.

Message statuses
StatusMeaning
scheduledWaiting for its scheduled time.
queuedAccepted; credits reserved; waiting to be sent.
submittingBeing handed to the operator network.
submittedAccepted by the operator network — not yet delivered.
deliveredThe handset confirmed receipt.
undeliverableThe network could not deliver it (for example an invalid or unreachable number).
expiredThe validity period ended before delivery.
rejectedRejected by the network or before sending.
cancelledCancelled before it was sent; credits released.
unknownNo final receipt arrived in the waiting period.

Idempotency keys

Send an Idempotency-Key header (up to 100 characters) with every send. A retry with the same key and body returns the original response, flagged Idempotent-Replayed: true; the same key with a different body returns 409 idempotency_key_reused. Keys are remembered for 24 hours, so a network timeout can never send an SMS twice.

Errors and limits

  • Every error has the same JSON shape with a stable code — for example insufficient_credits (402), validation_failed (422) or rate_limited (429).
  • Every response carries X-Request-Id and X-RateLimit-* headers; a 429 adds Retry-After.
  • The default limit is 20 requests per second per key (burst 40), and message throughput is also capped per organisation.
  • Unknown JSON fields are rejected, and request bodies are limited to 1 MiB.

Webhooks and signature verification

LundoSMS sends a signed POST to your server on every message status change. Verify the signature before you trust the body.

Headers on every delivery

Headers on every delivery
FieldMeaning
X-LundoSMS-EventThe event type, for example message.status.
X-LundoSMS-DeliveryA UUID that stays the same across retries — use it to ignore duplicates.
X-LundoSMS-Signaturet=<unix time>,v1=<hex HMAC-SHA256>

How to verify a delivery

  1. Take the raw request body, before any JSON parsing.
  2. Read t and v1 from the X-LundoSMS-Signature header.
  3. Compute HMAC-SHA256 of <t>.<raw body> with your webhook secret and compare it with v1 using a constant-time comparison.
  4. Reject the request if t is more than five minutes old.

X-LundoSMS-Signature

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyLundoSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const age = Math.abs(Date.now() / 1000 - Number(parts.t));
  if (!parts.t || !parts.v1 || age > 300) return false; // older than 5 minutes

  const expected = createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(parts.v1);
  return a.length === b.length && timingSafeEqual(a, b);
}

A non-2xx response or a timeout over 10 seconds is retried after 10 s, 30 s, 2 min, 10 min, 30 min, 1 h, 3 h and 6 h — eight attempts — and then the delivery is marked dead and shown in your dashboard with a retry button.

Example event

POST https://your-server.example/webhooks/lundosms
{
  "id": "evt_0192c0e5a1b2",
  "type": "message.status",
  "created_at": "2026-10-17T09:31:04Z",
  "data": {
    "message_id": "0192c0d1-7c3e-7a52-9f31-2b6c8a1d4e91",
    "to": "+255712345678",
    "status": "delivered",
    "segments": 1,
    "client_reference": "ORDER-1001",
    "error_code": null,
    "submitted_at": "2026-10-17T09:30:01Z",
    "delivered_at": "2026-10-17T09:31:03Z"
  }
}

Test before you go live

Test keys (lsk_test_…) run the same validation and return real references, with simulated delivery receipts. No SMS leaves the platform and no credits are used. Switch to a live key when you are ready.

More inside your dashboard

The Developer section of your dashboard shows your own base URL, approved sender IDs and keys, the OpenAPI reference and ready-to-copy samples in cURL, Go, PHP, Node.js and Python.

Open the developer docs

API questions

Naweza kutuma kutoka kwenye mfumo wangu mwenyewe?

Ndiyo. REST API inapokea ombi moja la HTTPS lenye ufunguo wa API, inasaidia funguo za idempotency ili kujaribu tena kusisababishe kutuma mara mbili, na inaripoti mabadiliko ya hali kwa webhook iliyosainiwa. Funguo za majaribio zinakuwezesha kujaribu kila kitu bila kutuma ujumbe halisi.

Naweza kujaribu kabla ya kununua salio?

Ndiyo. Fungua akaunti kisha tumia ufunguo wa API wa majaribio: unapitia ukaguzi uleule na kurudisha marejeo halisi, lakini hakuna kinachotumwa kwenye simu na hakuna salio linalotumika.

Nitajuaje kama ujumbe umefika?

Kila ujumbe una hali yake: uko kwenye foleni, umetumwa kwenye mtandao, umefika, au umeshindikana. "Umetumwa kwenye mtandao" inamaanisha mtandao umeukubali ujumbe; "umefika" inamaanisha simu ya mpokeaji imethibitisha. Unaweza kuona hali hizi kwenye dashibodi, kuzipakua, au kuzipokea kwa webhook au SMPP. Simu iliyozimwa au isiyo na mtandao inaweza kupokea ujumbe baadaye au isiupokee kabisa — hakuna mtoa huduma anayeweza kuhakikisha kufika pale mtandao usipothibitisha.

Uunganishaji wa SMPP →

Uko tayari kufikia kila simu nchini Tanzania?

Fungua akaunti, sajili jina la mtumaji na utume ujumbe wako wa kwanza leo. Nunua salio la SMS unalohitaji — halikwisha muda.

Unalipia salio kabla ya kutumia. Bei kwa TZS, VAT imejumuishwa.