快速開始
- 1. 建立密鑰。 在工作台中開啟API及網絡掛鉤功能。首次充值後可獲取密鑰;每組密鑰只顯示一次,請即時複製或儲存為文字檔。
- 2. 檢查費用。 將相同內容POST至
/costs,即可查看收件人、分段及總費用(無需實際發送)。 - 3. 發送。 將內容POST至
/sends。回覆包含發送ID;訊息會立即或於send_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"];身份驗證
在Authorization標頭中傳送密鑰:Authorization: Bearer sso_live_...。密鑰為49個字符,以sso_live_開頭。每個賬戶最多5組密鑰;在工作台撤銷密鑰後將立即失效。價目表為公開資訊,無需密鑰。
錯誤處理
錯誤會返回HTTP狀態碼及包含固定代碼的JSON正文,訊息可直接顯示予用戶。請求失敗時不會收取費用。
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" } }| 欄位 | 類型 | 含義 |
|---|---|---|
| 400 | invalid_json, text_missing, recipients_missing, too_many_recipients, too_many_segments, schedule_range, bad_send_at | 請求需要修改。 |
| 401 | unauthorized | 密鑰缺失、錯誤或已被撤銷。 |
| 402 | insufficient_credit | 賬戶餘額不足。 |
| 404 | not_found | 找不到相應的發送記錄、訊息、國家或端點。 |
| 409 | not_cancelable | 發送已經開始。 |
| 429 | rate_limited | 每分鐘超過 240 個請求。 |
限制
- 每個密匙每分鐘可發送 240 個請求,詳見 X-RateLimit-Limit、X-RateLimit-Remaining 及 X-RateLimit-Reset(Unix 時間戳)。
- 每個請求 5,000 個號碼,每條短信 2,000 個字符,每條消息 10 個分段。
- 每個帳戶每分鐘最多發送 300 條消息。
- send_at 從 10 分鐘到 30 天前。
價格
GET/api/v1/prices
GET/api/v1/prices/{country}
每個國家每條消息(一個分段)的價格,以美元計,保留五位小數。無需密鑰。
{ "price": { "country": "GB", "name": "United Kingdom", "dial_code": "+44", "price_usd": "0.00868" }, "updated": "2026-11-07" }賬戶餘額
GET/api/v1/account
{ "account": "SO8K2M4Q7X9TBA", "credit_usd": "41.27660", "currency": "USD" }估算費用
POST/api/v1/costs
使用與發送相同的內容並返回其成本,無需發送或收費。每個收件人自己的合併文本決定其分段,因此總計是準確的。
{
"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
}建立發送
POST/api/v1/sends
一條短信發送給一個或多個號碼。整個發送的信用在接收時扣除;失敗的消息會逐一退款。
| 欄位 | 類型 | 含義 |
|---|---|---|
| to | string or array | 國際格式的號碼(+447700900461)。使用此格式或收件人。 |
| recipients | array | 包含 to 和 fields 的對象,用於合併字段:{ "to": "+1...", "fields": { "first_name": "Ava" } }。 |
| text | string | 消息,最多 2,000 個字符。{{field}} 會根據每個收件人替換;缺失的值變為空。 |
| send_at | string | 可選的 ISO 8601 時間,帶偏移量或 Z。如果沒有,發送立即開始。 |
| name | string | 在工作台中顯示的可選標籤(80 個字符)。 |
| client_ref | string | 在 webhooks 中返回的可選參考(64 個字符)。 |
| opt_out | boolean | 將保存在設置中的退出行添加到文本末尾。 |
| default_country | string | 用於沒有國家代碼的號碼的 ISO 代碼。 |
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" } ]
}無法發送的號碼列在 dropped 中,並且不會收費。代碼:no_country_code, too_short, too_long, not_a_number, wrong_length, not_covered, duplicate 和 suppressed(在您的禁止發送列表中)。
獲取或列出發送記錄
GET/api/v1/sends/{id}
GET/api/v1/sends?limit=25&before={id}
單次發送包括每個消息狀態的 counts。列表按最新排序;將 next_before 作為 before 傳遞以獲取下一頁。來自工作台的發送也會出現在這裡,帶有 source: "web"。
訊息
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" } }取消發送
POST/api/v1/sends/{id}/cancel
只有尚未開始的預定發送可以取消。全額費用返還到您的餘額,並且答案包括 refunded_usd。
網絡掛鉤
在工作台(API 和 webhooks)中設置一個 https URL。當消息送達或失敗時,我們會 POST 一個事件,無論是來自 API 還是工作台的發送。在 10 秒內以任何 2xx 狀態回應。失敗的送達會嘗試最多 7 次:立即,然後在 1 分鐘、5 分鐘、30 分鐘、1 小時、2 小時和 3 小時後。工作台顯示每次嘗試的狀態和回應。
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": "..." } } }簽名是 timestamp + "." + raw body 的 HMAC-SHA256,使用您的簽名密鑰(以 sso_whk_ 開頭)。在解析前檢查原始正文,拒絕超過 5 分鐘的時間戳,並使用送達 ID 忽略重複。
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);
}事件:message.delivered、message.failed,以及來自「發送測試事件」按鈕的test。
狀態及原因
| 欄位 | 類型 | 含義 |
|---|---|---|
| queued | status | 等待離開我們的隊列。 |
| sent | status | 已提交至網絡,等待發送報告。大多數報告會在幾分鐘內到達;部分網絡可能需要長達 24 小時。 |
| delivered | status | 網絡確認手機已接收。 |
| failed | status | 未送達。費用已退還至您的餘額。 |
| canceled | status | 因您取消而未發送。已退款。 |
失敗原因(reason 欄位):
| 欄位 | 類型 | 含義 |
|---|---|---|
| network_error | reason | 網絡錯誤 |
| absent | reason | 手機關機或不在服務範圍內 |
| not_in_service | reason | 號碼已停用 |
| blocked | reason | 被網絡或收件人屏蔽 |
| landline | reason | 固網電話或無法接收 SMS 的號碼 |
| content_filter | reason | 被網絡內容過濾器拒絕 |
| expired | reason | 已過期:在網絡時間限制前未收到報告 |
| canceled | reason | 在發送前取消 |
| unknown | reason | 未知網絡回應 |