台灣簡訊 API 串接教學 — 從註冊到發出第一則 +886 簡訊(cURL/Node.js/Python/PHP 範例)
發佈於
重點摘要
- 註冊帳號後在後台自助申請 API key,開通後就能自己測試,註冊即送 SMS 試用額度,免信用卡。
- 發到台灣門號只需要
country_code、mobile、content三個欄位,不用帶sender。- 同步回應的
rc: 100只代表平台收下請求,要在後台設定 Callback URL 才收得到提交與送達回報;第二段的delivery_status是SS才算送達。- 測試階段請用純文字內容:台灣含網址或電話的商業簡訊要先登錄白名單。
- 第一段 callback 回
IM多半是號碼格式問題,回IP是點數不足,兩種都不扣點。
這篇是寫給要把簡訊發送接進自己系統的工程師。目標很單純:用你自己的台灣門號,從零開始收到第一則由 API 發出的簡訊,並且確認它真的送達。整個流程不需要安裝 SDK,帳號和 API key 都是線上自助申請。
你需要準備的只有三樣:一支能收簡訊的台灣手機、一個終端機,以及任何一種能發 HTTP 請求的程式語言。
開始之前:發到台灣要注意的兩件事
不用帶發送者名稱。 台灣的企業簡訊以數字號碼顯示,所以發到 886 的請求不需要 sender 欄位,省略即可,帶了反而可能造成驗證失敗。這一點和香港相反,香港必須帶已登記的發送人名稱。
測試內容先用純文字。 自 2024 年 11 月 18 日起,台灣的商業簡訊如果含有網址、短網址或電話號碼,發送企業要先向電信業者或簡訊代發業者登錄白名單,未登錄的訊息不予發送(中央社報導)。只有文字的內容不受影響,所以驗證碼和測試訊息可以直接發。兩地規則的完整比較,詳見《SMS 是什麼?簡訊/短訊的原理、字數、費用與驗證碼用途(香港・台灣)》。
步驟一:註冊帳號
到 X Lead 註冊頁建立帳號。註冊即送 SMS 試用額度,免信用卡,介面是繁體中文。
UFOSEND 的發送平台與 API 由 X Lead 提供,所以帳號、後台和 API 網域都是 xleadfunnel.com。
步驟二:申請 API key
登入後台自助申請 API key,申請經我們審核開通後就可以使用。之後每個請求都在 header 帶上 x-api-key,沒有 OAuth 流程,也沒有要定期更新的 token。
API key 等同帳號的發送權限,請只放在伺服器端,不要寫進前端程式碼或推上公開的程式庫。下面的範例都從環境變數讀取:
export XLEAD_API_KEY="貼上你的 API key"
步驟三:先查點數餘額,確認 key 可用
發簡訊之前,先打一個不花點數的端點,確認 key 和網路都沒問題:
curl https://api.connect.xleadfunnel.com/v1/acct/get-points-balance \
-H "x-api-key: $XLEAD_API_KEY"
回應會帶回帳號剩餘的點數,SMS 與 MMS 共用同一池點數:
{
"rc": 100,
"rm": "Remaining points for sending SMS & MMS",
"points": "380.242"
}
rc 是 100 就代表 key 有效。
步驟四:發出第一則簡訊
端點是 POST /v1/sms/send,一個請求對應一位收件人。把 mobile 換成你自己的門號:
curl -X POST https://api.connect.xleadfunnel.com/v1/sms/send \
-H "Content-Type: application/json" \
-H "x-api-key: $XLEAD_API_KEY" \
-d '{
"country_code": "886",
"mobile": "0912345678",
"content": "這是我用 API 發出的第一則簡訊"
}'
三個欄位的規則:
| 欄位 | 必填 | 說明 |
|---|---|---|
country_code | 是 | 地區號碼,1–3 位數字,不含前置 0,也不要加 +。台灣是 886。 |
mobile | 是 | 收件人門號,最多 10 位數字。開頭的 0 帶不帶都可以,0912345678 和 912345678 都能送達。 |
content | 否 | 簡訊內容,UTF-8 編碼。 |
成功的話會立刻收到:
{
"rc": 100,
"rm": "OK",
"ref_id": "145737475"
}
ref_id 是這次請求的識別碼,請存起來,後面的 callback 會帶同一個值。
其他語言的寫法
同一個請求,用各語言內建或最常見的 HTTP 用戶端就能發。
Node.js
const res = await fetch('https://api.connect.xleadfunnel.com/v1/sms/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.XLEAD_API_KEY,
},
body: JSON.stringify({
country_code: '886',
mobile: '0912345678',
content: '這是我用 API 發出的第一則簡訊',
}),
});
const { rc, rm, ref_id } = await res.json();
if (rc !== 100) throw new Error(`send failed: ${rm}`);
console.log('queued as', ref_id);
Python
import os, requests
res = requests.post(
'https://api.connect.xleadfunnel.com/v1/sms/send',
headers={'x-api-key': os.environ['XLEAD_API_KEY']},
json={
'country_code': '886',
'mobile': '0912345678',
'content': '這是我用 API 發出的第一則簡訊',
},
timeout=10,
)
data = res.json()
if data['rc'] != 100:
raise RuntimeError(f"send failed: {data['rm']}")
print('queued as', data['ref_id'])
PHP
<?php
$ch = curl_init('https://api.connect.xleadfunnel.com/v1/sms/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . getenv('XLEAD_API_KEY'),
],
CURLOPT_POSTFIELDS => json_encode([
'country_code' => '886',
'mobile' => '0912345678',
'content' => '這是我用 API 發出的第一則簡訊',
], JSON_UNESCAPED_UNICODE),
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
if ($data['rc'] !== 100) {
throw new RuntimeException('send failed: ' . $data['rm']);
}
echo 'queued as ' . $data['ref_id'];
Java 與 C# 的寫法在 SMS API 文件,把香港範例的 sender 拿掉、country_code 改成 886 即可。
步驟五:確認真的送達
手機響了當然最直接,但系統上線後不會有人盯著手機看。rc: 100 只代表平台收下請求並排入佇列,後面還有兩段回報,都是推到你在後台設定的 Callback URL。沒有設定的話,這兩段回報都不會發出。
第一段:提交結果。 請求通過驗證並交給電信業者時,會收到 SMD,這一段決定是否扣點:
{
"ref_id": "120731617",
"rc": 100,
"rm": "A valid send, message has been submitted to carrier for delivery",
"status": "SMD",
"num_sms_count": 1,
"used_points": 0.36,
"remaining_points": 99.64
}
num_sms_count、used_points、remaining_points 分別是這次計費的則數、扣掉的點數和剩餘點數,數值以你帳號實際收到的為準。中文內容以 Unicode 編碼,單則上限是 70 個字,超過會分段計費,所以測試時順便看一下 num_sms_count 是否符合預期。
第二段:送達回報。 電信業者回報收件人手機的最終狀態後,平台會再推一次。這一段用 delivery_status 表示結果,SS 是已送達:
{
"ref_id": "120731617",
"rc": 100,
"rm": "Carrier has successfully delivered to the recipient's device",
"delivery_status": "SS"
}
SF 是未能送達,這時會多一個 desc_code 說明原因:
{
"ref_id": "120731617",
"rc": 100,
"rm": "Undelivered due to unreachable destination, network issues, or recipient",
"delivery_status": "SF",
"desc_code": "UNDELIV"
}
desc_code | 意思 |
|---|---|
EXPIRED | 超過有效期仍未能送達 |
UNDELIV | 無法送達,例如目的地不可達、網路問題或手機關機 |
REJECTD | 被拒,例如內容無效、列入黑名單或政策限制 |
UNKNWN | 電信業者無法判定最終送達狀態 |
注意兩段的欄位名稱不同:第一段看 status,第二段看 delivery_status。第一次串接時,建議先把收到的整個 payload 原樣記錄下來,確認欄位之後再寫判斷邏輯。一個最小的接收端長這樣:
import express from 'express';
const app = express();
app.use(express.json());
app.post('/sms-callback', (req, res) => {
console.log('callback', JSON.stringify(req.body));
res.sendStatus(200);
});
app.listen(3000);
Callback URL 必須是對外可達的 HTTPS 網址。同一則 callback 有可能重複送達,正式環境請用 ref_id 加狀態(第一段的 status、第二段的 delivery_status)當唯一鍵,寫成重複收到也不會出錯。每個狀態碼該怎麼處理,詳見《SMS DLR 狀態碼完全解讀 — SMD、DELIVRD、UNDELIV、REJECTD 到底代表什麼》。
沒收到?先對照這張表
第一段 callback 如果回 rc: -100,代表請求沒有送出,也不會扣點:
| 狀態 | 原因 | 怎麼處理 |
|---|---|---|
IM | 地區號碼或門號不正確 | 檢查 country_code 是不是 886(沒有 +、沒有前置 0),mobile 有沒有超過 10 位數字或混入空格、符號 |
IP | 點數不足 | 用步驟三的端點查餘額,再到後台儲值 |
IUL | 號碼在拒收名單(unsend list)中 | 這個號碼之前已被列為不發送,換一個門號測試 |
IS | 發送者名稱無效 | 發到台灣不要帶 sender 欄位 |
如果第一段回了 SMD,手機卻沒收到,就看第二段:delivery_status 是 SF 的話,再看 desc_code。UNDELIV 先確認手機有開機、有訊號;REJECTD 多半是內容問題,先確認內容沒有網址或電話號碼。
沒有收到任何 callback,通常不是簡訊的問題,而是 Callback URL 還沒在後台設定,或是你的網址對外連不到。
上線之前
測試跑通之後,正式上線前還有三件事:
- 內容要放連結或電話,先辦白名單登錄。 網域要和登錄資料相符,第三方短網址對不上登錄資料,應改用自己的網域。
- 驗證碼要做限流。 發送端點一旦公開,就可能被拿來灌量,詳見《OTP 簡訊實作的 6 個陷阱 — 從驗證碼設計到簡訊轟炸防護》。
- 號碼在入庫時就正規化。 資料庫裡存的應該是乾淨的
country_code和mobile兩欄,不要等發送時才處理使用者輸入的各種格式。
所有端點、參數與狀態碼的完整定義在 SMS API 文件。還沒有帳號的話,免費註冊並申請 API key,開通後就能用自己的門號測試;發送量較大或有特殊需求,可以聯絡我們。