SelanimDocs

Code samples

The same API in ten languages: what to install, how to authenticate, and the calls you will make first.

There is no SDK to install. The API is HTTPS and JSON, which every language on this page speaks with what it ships with or one small package — so the samples below are the whole integration, not a wrapper around it.

Pick a language once. The choice follows you across every code block on this site, including the quickstart and the endpoint pages.

What you need

cURL

curl, on every machine already.

Go

Go 1.21+. Standard library only — nothing to install.

Python

Python 3.9+, plus the requests package: pip install requests

Node.js

Node.js 18 or newer, where fetch is built in.

PHP

PHP 8.0+ with the cURL extension (ext-curl).

Java

JDK 17+. java.net.http ships with it.

C#

.NET 7+ for raw string literals. HttpClient is built in.

Ruby

Ruby 3.0+. net/http is in the standard library.

Rust

cargo add reqwest --features json, and cargo add tokio --features full

Dart / Flutter

dart pub add http — the same code runs inside Flutter.

Keep the key out of the code

Every sample reads SBS_API_KEY from the environment. A key pasted into a source file is a key in your git history, and rotating it later means finding every copy.

bash
# macOS / Linux
export SBS_API_KEY="sbs_test_..."

# Windows PowerShell
$env:SBS_API_KEY = "sbs_test_..."

Start on a test key

A sbs_test_ key runs validation, the sender ID check and your delivery webhooks without reaching an operator or costing credits. Everything below works against one — see Authentication.

Send a message

One call, one or many recipients. A 202 comes back with an id per recipient; delivery is reported later on your webhook.

bash
curl -X POST "https://bulksmsapi.selanim.com/v1/sms/send" \
  -H "Authorization: Bearer $SBS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "senderId": "SELANIM",
    "to": [
      "255712345678"
    ],
    "message": "Karibu Selanim Bulk SMS!"
  }'

Needs: curl, on every machine already.

Sending to several people is the same call with more numbers, and reference is your own tag, echoed back on every delivery event — the field that lets you reconcile a batch later.

bash
curl -X POST "https://bulksmsapi.selanim.com/v1/sms/send" \
  -H "Authorization: Bearer $SBS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "senderId": "SELANIM",
    "to": [
      "255712345678",
      "0754112233"
    ],
    "message": "Dear parent, Term 3 fees are due on 25th Aug.",
    "reference": "term3-fees"
  }'

Check the balance

Worth calling before a large send: if the wallet cannot cover the whole batch nothing is sent, and the call fails with insufficient_funds.

bash
curl -X GET "https://bulksmsapi.selanim.com/v1/wallet/balance" \
  -H "Authorization: Bearer $SBS_API_KEY"

Follow a message

Webhooks are the right way to learn what happened to a message. Polling is for the times you cannot receive one — a script, a laptop, a job behind a firewall.

bash
curl -X GET "https://bulksmsapi.selanim.com/v1/sms/SM-4a91c0" \
  -H "Authorization: Bearer $SBS_API_KEY"

Create a contact

Numbers are normalised before the duplicate check, so 0713111321 and +255 713 111 321 are the same person. Custom fields are what {{placeholders}} read when you personalise a send.

bash
curl -X POST "https://bulksmsapi.selanim.com/v1/contacts" \
  -H "Authorization: Bearer $SBS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "0713111321",
    "firstName": "Rehema",
    "lastName": "Mwakalinga",
    "customFields": {
      "class": "Form 4"
    }
  }'

Request a sender ID

The name recipients see. Approval is manual and involves the operator, so request it early and keep sending under an approved name meanwhile — see Sender IDs.

bash
curl -X POST "https://bulksmsapi.selanim.com/v1/sender-ids" \
  -H "Authorization: Bearer $SBS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "SHULEYETU",
    "purpose": "Fee reminders and results notifications for parents."
  }'

Register a webhook

Creates the endpoint on the first call and replaces it afterwards. The signing secret is returned once, on the call that creates it.

bash
curl -X PUT "https://bulksmsapi.selanim.com/v1/webhooks" \
  -H "Authorization: Bearer $SBS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://brightschool.co.tz/hooks/sbs-delivery",
    "events": [
      "message.delivered",
      "message.failed"
    ]
  }'

Verify a webhook signature

Every delivery event carries X-SBS-Signature: sha256= followed by the hex HMAC-SHA256 of {timestamp}.{rawBody}. Recompute it and compare in constant time; reject anything whose X-SBS-Timestamp is more than five minutes old.

bash
# Recompute the signature over the exact bytes you received.
# Reject first if X-SBS-Timestamp is more than five minutes old.

EXPECTED="sha256=$(printf '%s.%s' "$SBS_TIMESTAMP" "$(cat body.json)" \
  | openssl dgst -sha256 -hmac "$SBS_WEBHOOK_SECRET" -r \
  | cut -d' ' -f1)"

[ "$EXPECTED" = "$SBS_SIGNATURE" ] && echo "ok" || echo "rejected"

Hash the raw body

Parse the JSON and re-serialise it and the bytes change — a reordered key or a dropped space is enough — so the signature will never match. Keep the untouched body until after the check. The full flow, including retries and replay windows, is on Delivery webhooks.

Prefer clicking to typing?

Every call here is also a request in the Postman collection — see Postman & OpenAPI, or generate a client of your own from openapi.yaml.