API Documentation

The Sahla SMS API is a REST API over HTTPS. Every request is authenticated with an API key and returns JSON. Base URL:

https://sms.sahlapay.dz/api/public/v1

Quick start

Pair an Android device in your dashboard, create an API key, then send your first message. The device ID is optional — Sahla SMS automatically selects an online device when you omit it.

bash
1curl -X POST https://sms.sahlapay.dz/api/public/v1/send-sms \
2 -H "Content-Type: application/json" \
3 -H "x-api-key: $SAHLA_API_KEY" \
4 -d '{
5 "recipients": ["+1234567890"],
6 "message": "Hello from Sahla SMS!"
7 }'

Authentication

Pass your API key in the x-api-key header on every request. Keys are shown once when created — store them securely on your server and never expose them in browser code.

http
x-api-key: sk_live_3f9a...c21d

Keys can be limited to sending SMS, reading SMS, reading devices, and managing webhooks. Revoking a key takes effect immediately.

Send a single SMS

POST/send-sms

request
1{
2 "recipients": ["+1234567890"],
3 "message": "Your verification code is 481932",
4 "deviceId": "dev_8c21f0" // optional
5}
response 200
1{
2 "success": true,
3 "data": {
4 "messageId": "msg_7b12ce",
5 "status": "queued",
6 "segments": 1,
7 "recipients": 1,
8 "queuedAt": "2026-01-14T09:21:44Z"
9 }
10}

Send bulk SMS

POST/send-sms

Pass up to 1,000 recipients per request, or use a saved contact group. Messages are queued and sent steadily by your device with a short pause between each.

request
1{
2 "recipients": ["+1234567890", "+212612345678"],
3 "groupId": "grp_4a91bd", // optional, merged with recipients
4 "message": "Hi {{name}}, your order has shipped.",
5 "variables": { "name": "Sam" }
6}
response 200
1{
2 "success": true,
3 "data": {
4 "batchId": "bat_19fd03",
5 "queued": 2,
6 "rejected": 0,
7 "estimatedCompletion": "2026-01-14T09:24:10Z"
8 }
9}

List devices

GET/devices

response 200
1{
2 "success": true,
3 "data": [
4 {
5 "id": "dev_8c21f0",
6 "name": "Pixel 6a",
7 "phoneNumber": "+1234567890",
8 "status": "online",
9 "batteryLevel": 87,
10 "signalStrength": 4,
11 "lastSeenAt": "2026-01-14T09:21:02Z",
12 "messagesSent": 1842
13 }
14 ]
15}

Message status

GET/message-status

A message moves through queued, sent, then delivered. A failed delivery returns failed with an error reason.

response 200
1{
2 "success": true,
3 "data": {
4 "id": "msg_7b12ce",
5 "recipient": "+1234567890",
6 "status": "delivered",
7 "segments": 1,
8 "queuedAt": "2026-01-14T09:21:44Z",
9 "sentAt": "2026-01-14T09:21:47Z",
10 "deliveredAt": "2026-01-14T09:21:52Z"
11 }
12}

Receive SMS

GET/devices

Enable SMS receiving in the Android app, then retrieve incoming messages or subscribe to the sms.received webhook for real-time delivery.

Webhooks

Register a webhook URL in the dashboard and select the events you need. Every request includes an x-sahla-signature header containing an HMAC-SHA256 signature of the raw body using your signing secret.

payload
1{
2 "event": "sms.delivered",
3 "timestamp": "2026-01-14T09:21:52Z",
4 "data": {
5 "messageId": "msg_7b12ce",
6 "deviceId": "dev_8c21f0",
7 "recipient": "+1234567890",
8 "status": "delivered"
9 }
10}
verify (Node.js)
1import { createHmac, timingSafeEqual } from 'crypto'
2
3const expected = createHmac('sha256', process.env.SIGNING_SECRET)
4 .update(rawBody)
5 .digest('hex')
6
7const ok = timingSafeEqual(
8 Buffer.from(signatureHeader),
9 Buffer.from(expected),
10)

Failed deliveries are retried with increasing delays for up to 24 hours. Respond with any 2xx status within 10 seconds to acknowledge receipt.

Errors & rate limits

Errors return a non-2xx status with a JSON body. The API allows 60 requests per minute per key; exceeding this returns 429 with a Retry-After header.

response 400
1{
2 "success": false,
3 "error": {
4 "code": "invalid_recipient",
5 "message": "Recipient +12345 is not a valid E.164 phone number"
6 }
7}
  • 401 unauthorized — API key is missing, revoked, or malformed
  • 403 insufficient_scope — the key lacks the required permission
  • 409 no_device_available — no paired device is currently online
  • 422 quota_exceeded — the monthly plan limit has been reached