Mora SMS API

A powerful and easy-to-use SMS API for sending messages, managing sender names, and checking account balances.

Base URL: https://mora-sa.com/api/v1/

🚀 Introduction

Welcome to the Mora SMS API documentation. Our RESTful API allows you to integrate SMS functionality into your applications with ease. Whether you need to send notifications, verification codes, or marketing messages, our API provides reliable and fast delivery.

Key Features

  • Send SMS messages to single or multiple recipients
  • Schedule messages for future delivery
  • Check account balance in real-time
  • Manage sender names
  • Comprehensive error handling
  • JSON and text response formats

🔐 Authentication

All API requests require authentication using your unique api_key and username. These parameters must be included in every request.

Authentication Example
https://mora-sa.com/api/v1/sendsms?api_key=your_api_key&username=your_username
⚠️ Important: Keep your API key secure and never expose it in client-side code. Store it securely on your server.

📱 Sending SMS

Send SMS messages to one or multiple recipients using the /sendsms endpoint.

Endpoint

POST/GET
https://mora-sa.com/api/v1/sendsms

Parameters

Parameter Type Description
message Required string The SMS message content to be sent
sender Required string The sender name/ID to be used for sending
numbers Required string Comma-separated list of phone numbers (e.g., "966501234567,966507654321")
datetime Optional string Schedule message for future delivery (format: YYYY-MM-DD HH:MM:SS)
return Optional string Response format: "json" (default) or "text"

Code Examples

curl -X POST "https://mora-sa.com/api/v1/sendsms" \
      -d "api_key=your_api_key" \
      -d "username=your_username" \
      -d "message=Hello from Mora SMS API!" \
      -d "sender=YourApp" \
      -d "numbers=966501234567,966507654321" \
      -d "return=json"
<?php
    $url = 'https://mora-sa.com/api/v1/sendsms';
    $data = array(
        'api_key' => 'your_api_key',
        'username' => 'your_username',
        'message' => 'Hello from Mora SMS API!',
        'sender' => 'YourApp',
        'numbers' => '966501234567,966507654321',
        'return' => 'json'
    );
    
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    echo $response;
    ?>
import requests
    
url = 'https://mora-sa.com/api/v1/sendsms'
data = {
    'api_key': 'your_api_key',
    'username': 'your_username',
    'message': 'Hello from Mora SMS API!',
    'sender': 'YourApp',
    'numbers': '966501234567,966507654321',
    'return': 'json'
}

response = requests.post(url, data=data)
print(response.json())
const data = new URLSearchParams({
    api_key: 'your_api_key',
    username: 'your_username',
    message: 'Hello from Mora SMS API!',
    sender: 'YourApp',
    numbers: '966501234567,966507654321',
    return: 'json'
});

fetch('https://mora-sa.com/api/v1/sendsms', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: data
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

💰 Balance Inquiry

Check your account balance using the /balance endpoint.

Endpoint

GET
https://mora-sa.com/api/v1/balance

Code Examples

curl "https://mora-sa.com/api/v1/balance?api_key=your_api_key&username=your_username"
<?php
    $url = 'https://mora-sa.com/api/v1/balance';
    $params = array(
        'api_key' => 'your_api_key',
        'username' => 'your_username'
    );
    
    $url .= '?' . http_build_query($params);
    $response = file_get_contents($url);
    echo $response;
    ?>
import requests
    
url = 'https://mora-sa.com/api/v1/balance'
params = {
    'api_key': 'your_api_key',
    'username': 'your_username'
}

response = requests.get(url, params=params)
print(response.json())
const params = new URLSearchParams({
    api_key: 'your_api_key',
    username: 'your_username'
});

fetch(`https://mora-sa.com/api/v1/balance?${params}`)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

👤 Sender Names

Retrieve available sender names for your account using the /sender_names endpoint.

Endpoint

GET
https://mora-sa.com/api/v1/sender_names

Code Examples

curl "https://mora-sa.com/api/v1/sender_names?api_key=your_api_key&username=your_username"
<?php
    $url = 'https://mora-sa.com/api/v1/sender_names';
    $params = array(
        'api_key' => 'your_api_key',
        'username' => 'your_username'
    );
    
    $url .= '?' . http_build_query($params);
    $response = file_get_contents($url);
    echo $response;
    ?>
import requests
    
url = 'https://mora-sa.com/api/v1/sender_names'
params = {
    'api_key': 'your_api_key',
    'username': 'your_username'
}

response = requests.get(url, params=params)
print(response.json())
const params = new URLSearchParams({
    api_key: 'your_api_key',
    username: 'your_username'
});

fetch(`https://mora-sa.com/api/v1/sender_names?${params}`)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

⚠️ Error Codes

The API returns specific error codes to help you troubleshoot issues. Here's a complete list of possible response codes:

Code Description
100 ✅ Numbers received successfully
105 ❌ Insufficient balance
106 ❌ Sender name not available
107 ❌ Sender name is blocked
108 ❌ No valid numbers for sending
112 ❌ Message contains prohibited words
114 ❌ Account is suspended
115 ❌ Mobile number not activated
116 ❌ Email not activated
117 ❌ Empty message cannot be sent
118 ❌ Sender name is empty
119 ❌ No recipient number provided

🔗 Webhook Integration

The platform can push the status of every message you send to a URL of your own (your "webhook"). Instead of polling the API, you receive an HTTP POST with the message and its per-number statuses.

1. Enable your webhook

  1. Log in to the portal and open API Keys (/api_keys).
  2. In the Webhook Settings card, enter your endpoint URL.
  3. Press Activate. (To stop receiving calls, press Disable on the same card).

URL Requirements

Rule Detail
Scheme http or https only (use https)
Host Must be public - private, loopback, and link-local addresses (127.0.0.1, 10.x, 192.168.x, 169.254.x, ...) are rejected
Length Max 2048 characters
Redirects Not followed - give the final URL, a 301/302 counts as a failure
⚠️ Error Notice: If the URL fails these checks the portal shows: "The webhook URL is invalid. Only public http or https URLs are allowed." and nothing is saved.

2. What we send

A single POST per message, with Content-Type: application/json.

POST Payload Example
{
  "msg_id": 8842137,
  "sending_time": "2026-08-24 10:15:32",
  "numbers_count": 3,
  "status": "delivered",
  "numbers": [
    { "number": "966500000001", "status": "delivered" },
    { "number": "966500000002", "status": "sending" },
    { "number": "966500000003", "status": "failed", "response": "رقم الجوال خطأ" }
  ]
}

Payload Fields

Field Type Description
msg_id integer The message id, same id returned by the send API. Use it as the idempotency key.
sending_time string YYYY-MM-DD HH:MM:SS, last update time of the message (server time).
numbers_count integer How many entries are in numbers.
status string Overall status of the message.
numbers array One entry per recipient.
numbers[].number string The recipient number.
numbers[].status string Status of that recipient.
numbers[].response string Only present when the status is failed; the reason (gateway or validation error text, may be Arabic).

Status Values

Value Meaning
pending Accepted, not handed to a gateway yet (may be awaiting approval or queued).
sending Handed to the gateway, in progress.
delivered Successfully sent / accepted by the gateway.
failed Rejected or not sent - see response for the reason.
ℹ️ Note: delivered means the gateway accepted the message. Operator delivery receipts (DLR) are not included in the webhook payload; check the reports screen or the API if you need the final DLR.

3. How your endpoint must behave

  • Answer 2xx: Any other status code (4xx, 5xx), a redirect, a TLS error, or a timeout is treated as a delivery failure.
  • Answer fast: Timeouts are 5 s to connect and 10 s in total. Queue the payload and process it asynchronously - don't do slow work in the handler.
  • Be idempotent: A call may repeat (for example if we time out after your server already processed it). Deduplicate on msg_id.
  • Ordering: Don't rely on ordering guarantees beyond msg_id. Messages are pushed in ascending msg_id order, one at a time.

Securing your endpoint

No signature or authorization header is sent. Protect the endpoint by:

  • Using a long unguessable path or query token, e.g., https://example.com/hooks/sms/9f2c1b7e5a..., and rejecting anything else.
  • Serving it over HTTPS.
  • Optionally allowlisting the platform's outbound IP address.

4. Delivery, retries and auto-disable

Behaviour Value
Schedule Every minute
Messages per run Up to 500 per account; the rest go out on the next runs
Retry on failure The same message is retried every minute, position is not advanced
Auto-disable After 10 consecutive failed runs (~10 minutes of downtime)
Maximum replay window 2 days - anything older is skipped permanently

Each message is pushed once, shortly after it is created - the payload shows the statuses as they are at that moment, not a final report.

⚠️ Webhook Status Notification: If your endpoint stays down, the webhook is switched off, the portal shows غير مفعل (inactive) next to the URL, and you get the notification: "تم ايقاف ال webhook لعدم القدره على الوصول للرابط". Fix your endpoint and press Activate again; messages queued while it was off are still delivered, up to the 2-day window.

5. Example receivers

<?php
// https://example.com/hooks/sms/<secret>
if (($_GET['token'] ?? '') !== 'YOUR_SECRET') {
    http_response_code(404);
    exit;
}

$payload = json_decode(file_get_contents('php://input'), true);
http_response_code(200); // answer first, work later
fastcgi_finish_request();

foreach ($payload['numbers'] as $n) {
    // store/update by $payload['msg_id'] + $n['number']
    // $n['status'], $n['response'] ?? null
}
?>
// routes/api.php
Route::post('hooks/sms/{token}', function (Request $request, $token) {
    abort_unless(hash_equals(config('services.sms.hook_token'), $token), 404);
    ProcessSmsWebhook::dispatch($request->all()); // queue it
    return response()->json(['ok' => true]);
});
app.post('/hooks/sms/:token', express.json(), (req, res) => {
    if (req.params.token !== process.env.SMS_HOOK_TOKEN) return res.sendStatus(404);
    
    res.sendStatus(200); // ack immediately
    queue.add('sms-status', req.body);
});

6. Webhook log

Every delivery attempt is recorded and shown in the portal under API Keys → Webhook Log (/webhook/logs). Each row holds the time, the message id (linking to the message), whether the call succeeded, the HTTP status code your endpoint returned, how long it took, and the error text when it failed. You can filter by outcome (delivered / failed) or by a specific message id.

Attempts are kept for 7 days and pruned automatically.

Use it to answer the usual questions without contacting support: did the call reach my server? (a row exists), what did my server answer? (HTTP Code), why was my webhook disabled? (a run of failed rows), and is my endpoint too slow? (Duration approaching 10 000 ms).

7. Troubleshooting

Symptom Likely cause
URL rejected when saving Non-public host, wrong scheme, or DNS does not resolve
No calls arriving Webhook disabled (غير مفعل) or no new messages since it was activated
Webhook keeps disabling itself Endpoint returns non-2xx, redirects, or takes longer than 10 s
Some numbers missing response Normal - response is only sent for failed entries
No row in the Webhook Log at all The message predates activation, or the webhook is disabled
Statuses look "too early" Expected - the push happens within a minute of sending; use the reports/API for final DLR