Mulakan cepat
- 1. Buat kunci. Dalam ruang kerja, buka API dan webhook. Kunci akan dibuka selepas top-up pertama anda dikreditkan; setiap kunci hanya ditunjukkan sekali, jadi salin atau simpan sebagai fail teks.
- 2. Semak kos. POST badan yang sama ke
/costsuntuk melihat penerima, segmen dan jumlah tanpa menghantar. - 3. Hantar. POST ke
/sends. Jawapan mengandungi ID penghantaran; mesej akan dihantar serta-merta 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"];Pengesahan
Hantar kunci anda dalam header Pengesahan: Authorization: Bearer sso_live_.... Kunci terdiri daripada 49 aksara dan bermula dengan sso_live_. Sehingga 5 kunci setiap akaun; batalkan satu dalam ruang kerja dan ia akan berhenti berfungsi serta-merta. Senarai harga adalah awam dan tidak memerlukan kunci.
Ralat
Ralat menggunakan kod status HTTP dan badan JSON dengan kod stabil dan ayat yang boleh anda tunjukkan kepada seseorang. Tiada bayaran dikenakan apabila 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" } }| Medan | Jenis | Maksud |
|---|---|---|
| 400 | invalid_json, text_missing, recipients_missing, too_many_recipients, too_many_segments, schedule_range, bad_send_at | Permintaan memerlukan perubahan. |
| 401 | unauthorized | Kunci hilang, salah atau dibatalkan. |
| 402 | insufficient_credit | Kredit tidak mencukupi untuk jumlah tersebut. |
| 404 | not_found | Tiada penghantaran, mesej, negara atau titik akhir seperti itu. |
| 409 | not_cancelable | Penghantaran telah bermula. |
| 429 | rate_limited | Lebih daripada 240 permintaan dalam seminit. |
Had
- 240 permintaan seminit setiap kunci, ditunjukkan dalam X-RateLimit-Limit, X-RateLimit-Remaining dan X-RateLimit-Reset (saat unix).
- 5,000 nombor setiap permintaan, 2,000 aksara setiap teks, 10 segmen setiap mesej.
- Mesej keluar sehingga 300 seminit setiap akaun.
- send_at dari 10 minit hingga 30 hari ke hadapan.
Harga
GET/api/v1/prices
GET/api/v1/prices/{country}
Harga setiap mesej (satu segmen) untuk setiap negara, dalam USD dengan 5 perpuluhan. Tiada kunci diperlukan.
{ "price": { "country": "GB", "name": "United Kingdom", "dial_code": "+44", "price_usd": "0.00868" }, "updated": "2026-11-07" }Kredit akaun
GET/api/v1/account
{ "account": "SO8K2M4Q7X9TBA", "credit_usd": "41.27660", "currency": "USD" }Anggar kos
POST/api/v1/costs
Mengambil badan yang sama seperti penghantaran dan mengembalikan kosnya, tanpa menghantar atau mengecaj. Teks gabungan setiap penerima menentukan segmennya, jadi jumlahnya tepat.
{
"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 penghantaran
POST/api/v1/sends
Satu teks kepada satu atau banyak nombor. Kredit untuk keseluruhan penghantaran diambil apabila ia diterima; mesej yang gagal dibayar balik satu demi satu.
| Medan | Jenis | Maksud |
|---|---|---|
| to | string or array | Nombor dalam format antarabangsa (+447700900461). Gunakan ini atau penerima. |
| recipients | array | Objek dengan to dan fields, untuk medan gabungan: { "to": "+1...", "fields": { "first_name": "Ava" } }. |
| text | string | Mesej, sehingga 2,000 aksara. {{field}} digantikan setiap penerima; nilai yang hilang menjadi kosong. |
| send_at | string | Masa ISO 8601 pilihan dengan offset atau Z. Tanpanya, penghantaran bermula serta-merta. |
| name | string | Label pilihan yang ditunjukkan dalam ruang kerja (80 aksara). |
| client_ref | string | Rujukan pilihan yang dikembalikan dalam webhooks (64 aksara). |
| opt_out | boolean | Menambahkan baris opt-out yang disimpan dalam Tetapan ke hujung teks. |
| default_country | string | Kod ISO digunakan untuk nombor yang ditulis tanpa kod 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" } ]
}Nombor yang tidak dapat dihantar disenaraikan dalam dropped dan tidak pernah dicaj. Kod: no_country_code, too_short, too_long, not_a_number, wrong_length, not_covered, duplicate dan suppressed (dalam senarai jangan hantar anda).
Dapatkan atau senaraikan penghantaran
GET/api/v1/sends/{id}
GET/api/v1/sends?limit=25&before={id}
Satu penghantaran termasuk counts setiap status mesej. Senarai adalah yang terbaru dahulu; lulus next_before sebagai before untuk halaman seterusnya. Penghantaran dari ruang kerja juga muncul di sini, dengan source: "web".
Mesej
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" } }Batal penghantaran
POST/api/v1/sends/{id}/cancel
Hanya penghantaran yang dijadualkan yang belum bermula boleh dibatalkan. Kos penuh dikembalikan ke kredit anda dan jawapan termasuk refunded_usd.
Webhook
Tetapkan URL https dalam ruang kerja (API dan webhooks). Kami POST satu peristiwa apabila mesej dihantar atau gagal, untuk penghantaran dari API dan ruang kerja. Jawab dengan sebarang status 2xx dalam 10 saat. Penghantaran yang gagal dicuba semula sehingga 7 kali: serta-merta, kemudian selepas 1 minit, 5 minit, 30 minit, 1 jam, 2 jam dan 3 jam. Ruang kerja menunjukkan setiap percubaan dengan status dan jawapannya.
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": "..." } } }Tandatangan adalah HMAC-SHA256 dari timestamp + "." + raw body dengan rahsia tandatangan anda (ia bermula dengan sso_whk_). Periksanya pada badan mentah sebelum menguraikan, tolak cap masa yang lebih lama daripada 5 minit, dan gunakan id penghantaran untuk mengabaikan ulangan.
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);
}Peristiwa: message.delivered, message.failed, dan test dari butang Hantar peristiwa ujian.
Status dan sebab
| Medan | Jenis | Maksud |
|---|---|---|
| queued | status | Menunggu untuk meninggalkan barisan kami. |
| sent | status | Dihantar ke rangkaian, menunggu laporan penghantaran. Kebanyakan laporan tiba dalam beberapa minit; sesetengah rangkaian mengambil masa sehingga 24 jam. |
| delivered | status | Rangkaian mengesahkan telefon telah menerimanya. |
| failed | status | Tidak dihantar. Harga telah dikembalikan ke saldo anda. |
| canceled | status | Tidak dihantar kerana anda membatalkan. Dikembalikan. |
Sebab kegagalan (medan reason):
| Medan | Jenis | Maksud |
|---|---|---|
| network_error | reason | Ralat rangkaian |
| absent | reason | Telefon dimatikan atau di luar liputan |
| not_in_service | reason | Nombor tidak berkhidmat |
| blocked | reason | Disekat oleh rangkaian atau penerima |
| landline | reason | Talian tetap atau nombor yang tidak boleh menerima SMS |
| content_filter | reason | Ditolak oleh penapis kandungan rangkaian |
| expired | reason | Tamat tempoh: tiada laporan sebelum had masa rangkaian |
| canceled | reason | Dibatalkan sebelum dihantar |
| unknown | reason | Tindak balas rangkaian tidak diketahui |
Semakan perkhidmatan terdapat di halaman status dan perubahan API dalam changelog.