Mulai cepat
- 1. Buat kunci. Di workspace, buka API dan webhooks. Kunci akan terbuka setelah top-up pertama Anda; setiap kunci hanya ditampilkan sekali, jadi salin atau simpan sebagai file teks.
- 2. Periksa biaya. POST body yang sama ke
/costsuntuk melihat penerima, segmen, dan total tanpa mengirim. - 3. Kirim. POST ke
/sends. Jawaban berisi id pengiriman; pesan akan dikirim segera atau padasend_at.
curl https://sendsmsonline.cc/api/v1/sends \
-H "Authorization: Bearer $SSO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "recipients": [ { "to": "+447700900461", "fields": { "first_name": "Tom" } }, { "to": "+12015550142", "fields": { "first_name": "Ava" } } ], "text": "Hi {{first_name}}, your table is ready at 7:30.", "name": "Friday bookings", "client_ref": "bookings-1107" }'const res = await fetch("https://sendsmsonline.cc/api/v1/sends", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SSO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
recipients: [{ to: "+447700900461", fields: { first_name: "Tom" } }],
text: "Hi {{first_name}}, your table is ready at 7:30.",
}),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error.message);
console.log(data.send.id, data.send.cost_usd);import os, requests
r = requests.post(
"https://sendsmsonline.cc/api/v1/sends",
headers={"Authorization": f"Bearer {os.environ['SSO_API_KEY']}"},
json={
"recipients": [{"to": "+447700900461", "fields": {"first_name": "Tom"}}],
"text": "Hi {{first_name}}, your table is ready at 7:30.",
},
timeout=30,
)
data = r.json()
if r.status_code >= 400:
raise RuntimeError(data["error"]["message"])
print(data["send"]["id"], data["send"]["cost_usd"])<?php
$ch = curl_init("https://sendsmsonline.cc/api/v1/sends");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("SSO_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"recipients" => [["to" => "+447700900461", "fields" => ["first_name" => "Tom"]]],
"text" => "Hi {{first_name}}, your table is ready at 7:30.",
]),
]);
$data = json_decode(curl_exec($ch), true);
echo $data["send"]["id"] ?? $data["error"]["message"];Autentikasi
Kirim kunci Anda di header Authorization: Authorization: Bearer sso_live_.... Kunci terdiri dari 49 karakter dan dimulai dengan sso_live_. Maksimal 5 kunci per akun; cabut satu di workspace dan kunci akan berhenti bekerja segera. Daftar harga bersifat publik dan tidak memerlukan kunci.
Kesalahan
Kesalahan menggunakan kode status HTTP dan body JSON dengan kode stabil dan kalimat yang dapat ditunjukkan kepada seseorang. Tidak ada biaya yang dikenakan saat permintaan gagal.
HTTP/1.1 402 Payment Required
{ "error": { "code": "insufficient_credit", "message": "Short by $3.20. Add funds to send.", "total": "8.62480", "credit": "5.42000" } }| Kolom | Tipe | Arti |
|---|---|---|
| 400 | invalid_json, text_missing, recipients_missing, too_many_recipients, too_many_segments, schedule_range, bad_send_at | Permintaan perlu diubah. |
| 401 | unauthorized | Kunci hilang, salah, atau dicabut. |
| 402 | insufficient_credit | Saldo tidak cukup untuk total biaya. |
| 404 | not_found | Tidak ada pengiriman, pesan, negara, atau endpoint seperti itu. |
| 409 | not_cancelable | Pengiriman sudah dimulai. |
| 429 | rate_limited | Lebih dari 240 permintaan dalam satu menit. |
Batasan
- 240 permintaan per menit per kunci, ditampilkan di X-RateLimit-Limit, X-RateLimit-Remaining, dan X-RateLimit-Reset (detik unix).
- 5,000 nomor per permintaan, 2,000 karakter per teks, 10 segmen per pesan.
- Pesan dikirim hingga 300 per menit per akun.
- send_at dari 10 menit hingga 30 hari ke depan.
Harga
GET/api/v1/prices
GET/api/v1/prices/{country}
Harga per pesan (satu segmen) untuk setiap negara, dalam USD dengan 5 desimal. Tidak perlu kunci.
{ "price": { "country": "GB", "name": "United Kingdom", "dial_code": "+44", "price_usd": "0.00868" }, "updated": "2026-11-07" }Saldo akun
GET/api/v1/account
{ "account": "SO8K2M4Q7X9TBA", "credit_usd": "41.27660", "currency": "USD" }Perkirakan biaya
POST/api/v1/costs
Mengambil body yang sama seperti pengiriman dan mengembalikan biayanya, tanpa mengirim atau menagih. Teks gabungan setiap penerima menentukan segmennya, sehingga totalnya akurat.
{
"recipients": 2, "segments_min": 1, "segments_max": 1, "encoding": "gsm7", "messages_billed": 2,
"total_usd": "0.01426", "credit_usd": "41.27660", "enough_credit": true,
"countries": [ { "country": "GB", "recipients": 1, "price_usd": "0.00868", "subtotal_usd": "0.00868" },
{ "country": "US", "recipients": 1, "price_usd": "0.00558", "subtotal_usd": "0.00558" } ],
"dropped": [], "too_long": null
}Buat pengiriman
POST/api/v1/sends
Satu teks ke satu atau banyak nomor. Saldo untuk seluruh pengiriman diambil saat diterima; pesan yang gagal dikembalikan satu per satu.
| Kolom | Tipe | Arti |
|---|---|---|
| to | string or array | Nomor dalam format internasional (+447700900461). Gunakan ini atau penerima. |
| recipients | array | Objek dengan to dan fields, untuk merge fields: { "to": "+1...", "fields": { "first_name": "Ava" } }. |
| text | string | Pesan, hingga 2,000 karakter. {{field}} diganti per penerima; nilai yang hilang menjadi kosong. |
| send_at | string | Waktu ISO 8601 opsional dengan offset atau Z. Tanpanya, pengiriman dimulai segera. |
| name | string | Label opsional yang ditampilkan di workspace (80 karakter). |
| client_ref | string | Referensi opsional yang dikembalikan di webhooks (64 karakter). |
| opt_out | boolean | Menambahkan baris opt-out yang disimpan di Pengaturan ke akhir teks. |
| default_country | string | Kode ISO digunakan untuk nomor yang ditulis tanpa kode negara. |
HTTP/1.1 201 Created
{
"send": { "id": "CA7Q29XMAB4DFK", "status": "queued", "source": "api", "recipients": 2, "segments_max": 1,
"encoding": "gsm7", "cost_usd": "0.01426", "refunded_usd": "0.00000", "client_ref": "bookings-1107",
"send_at": null, "created_at": "2026-11-07T10:12:03.120Z", "started_at": null, "finished_at": null },
"dropped": [ { "value": "+4412", "code": "too_short", "message": "Too short" } ]
}Nomor yang tidak dapat dikirim dicantumkan di dropped dan tidak pernah ditagih. Kode: no_country_code, too_short, too_long, not_a_number, wrong_length, not_covered, duplicate dan suppressed (pada daftar do-not-send Anda).
Dapatkan atau daftar pengiriman
GET/api/v1/sends/{id}
GET/api/v1/sends?limit=25&before={id}
Satu pengiriman mencakup counts per status pesan. Daftar diurutkan dari yang terbaru; lewati next_before sebagai before untuk halaman berikutnya. Pengiriman dari workspace juga muncul di sini, dengan source: "web".
Pesan
GET/api/v1/sends/{id}/messages?status=failed&limit=100
GET/api/v1/messages/{id}
{ "message": { "id": "MS3D8RW0K2QJ7N", "send_id": "CA7Q29XMAB4DFK", "to": "+447700900461", "country": "GB",
"segments": 1, "price_usd": "0.00868", "status": "delivered", "reason": null, "reason_text": null,
"created_at": "2026-11-07T10:12:03.120Z", "sent_at": "2026-11-07T10:12:04.010Z", "done_at": "2026-11-07T10:12:09.400Z" } }Batalkan pengiriman
POST/api/v1/sends/{id}/cancel
Hanya pengiriman terjadwal yang belum dimulai yang dapat dibatalkan. Biaya penuh dikembalikan ke saldo Anda dan jawabannya mencakup refunded_usd.
Webhooks
Atur URL https di workspace (API dan webhooks). Kami POST sebuah event saat pesan terkirim atau gagal, untuk pengiriman dari API dan workspace. Jawab dengan status 2xx apa pun dalam 10 detik. Pengiriman yang gagal dicoba lagi hingga 7 kali: segera, kemudian setelah 1 menit, 5 menit, 30 menit, 1 jam, 2 jam, dan 3 jam. Workspace menampilkan setiap percobaan dengan status dan jawabannya.
POST https://your-server.example/sms-events
X-SSO-Event: message.delivered
X-SSO-Delivery: evt_5a0c9e31d2b7f4a68c
X-SSO-Timestamp: 1794132729
X-SSO-Signature: sha256=8f1c...e42a
{ "id": "evt_5a0c9e31d2b7f4a68c", "event": "message.delivered", "created": "2026-11-07T10:12:09.000Z",
"data": { "message": { "id": "MS3D8RW0K2QJ7N", "send_id": "CA7Q29XMAB4DFK", "to": "+447700900461", "country": "GB",
"segments": 1, "price_usd": "0.00868", "status": "delivered", "reason": null, "reason_text": null,
"client_ref": "bookings-1107", "created_at": "...", "sent_at": "...", "done_at": "..." } } }Tanda tangan adalah HMAC-SHA256 dari timestamp + "." + raw body dengan rahasia tanda tangan Anda (dimulai dengan sso_whk_). Periksa pada raw body sebelum parsing, tolak timestamp yang lebih lama dari 5 menit, dan gunakan delivery id untuk mengabaikan pengulangan.
import crypto from "node:crypto";
// rawBody: the request body exactly as received (a string), before JSON.parse
export function verify(rawBody, headers, secret) {
const ts = headers["x-sso-timestamp"];
const sig = headers["x-sso-signature"] || "";
const want = "sha256=" + crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
return fresh && sig.length === want.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(want));
}import hashlib, hmac, time
def verify(raw_body: bytes, headers, secret: str) -> bool:
ts = headers.get("X-SSO-Timestamp", "")
sig = headers.get("X-SSO-Signature", "")
mac = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256)
want = "sha256=" + mac.hexdigest()
fresh = ts.isdigit() and abs(time.time() - int(ts)) < 300
return fresh and hmac.compare_digest(sig, want)<?php
function verify(string $rawBody, string $ts, string $sig, string $secret): bool {
$want = "sha256=" . hash_hmac("sha256", $ts . "." . $rawBody, $secret);
return ctype_digit($ts) && abs(time() - (int)$ts) < 300 && hash_equals($want, $sig);
}Event: message.delivered, message.failed, dan test dari tombol Kirim test event.
Status dan alasan
| Kolom | Tipe | Arti |
|---|---|---|
| queued | status | Menunggu untuk meninggalkan antrian kami. |
| sent | status | Telah dikirim ke jaringan, menunggu laporan pengiriman. Sebagian besar laporan tiba dalam beberapa menit; beberapa jaringan membutuhkan waktu hingga 24 jam. |
| delivered | status | Jaringan mengonfirmasi bahwa ponsel telah menerimanya. |
| failed | status | Tidak terkirim. Harga telah dikembalikan ke saldo Anda. |
| canceled | status | Tidak dikirim karena Anda membatalkan. Dana telah dikembalikan. |
Alasan kegagalan (kolom reason):
| Kolom | Tipe | Arti |
|---|---|---|
| network_error | reason | Kesalahan jaringan |
| absent | reason | Ponsel mati atau di luar jangkauan |
| not_in_service | reason | Nomor tidak aktif |
| blocked | reason | Diblokir oleh jaringan atau penerima |
| landline | reason | Nomor telepon rumah atau nomor yang tidak dapat menerima SMS |
| content_filter | reason | Ditolak oleh filter konten jaringan |
| expired | reason | Kedaluwarsa: tidak ada laporan sebelum batas waktu jaringan |
| canceled | reason | Dibatalkan sebelum dikirim |
| unknown | reason | Respon jaringan tidak diketahui |
Pemeriksaan layanan ada di halaman status dan perubahan API di changelog.