台灣簡訊 API 串接教學 — 從註冊到發出第一則 +886 簡訊(cURL/Node.js/Python/PHP 範例)

本文目錄
  1. 開始之前:發到台灣要注意的兩件事
  2. 步驟一:註冊帳號
  3. 步驟二:申請 API key
  4. 步驟三:先查點數餘額,確認 key 可用
  5. 步驟四:發出第一則簡訊
  6. 步驟五:確認真的送達
  7. 沒收到?先對照這張表
  8. 上線之前

重點摘要

  • 註冊帳號後在後台自助申請 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 還沒在後台設定,或是你的網址對外連不到。

上線之前

測試跑通之後,正式上線前還有三件事:

  1. 內容要放連結或電話,先辦白名單登錄。 網域要和登錄資料相符,第三方短網址對不上登錄資料,應改用自己的網域。
  2. 驗證碼要做限流。 發送端點一旦公開,就可能被拿來灌量,詳見《OTP 簡訊實作的 6 個陷阱 — 從驗證碼設計到簡訊轟炸防護》。
  3. 號碼在入庫時就正規化。 資料庫裡存的應該是乾淨的 country_code 和 mobile 兩欄,不要等發送時才處理使用者輸入的各種格式。

所有端點、參數與狀態碼的完整定義在 SMS API 文件。還沒有帳號的話,免費註冊並申請 API key,開通後就能用自己的門號測試;發送量較大或有特殊需求,可以聯絡我們。