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.
# macOS / Linux
export SBS_API_KEY="sbs_test_..."
# Windows PowerShell
$env:SBS_API_KEY = "sbs_test_..."Start on a test key
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.
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.
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.
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.
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.
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.
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.
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.
# 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
Prefer clicking to typing?
openapi.yaml.
