KedaSMS

可達SMS 開發者快速上手

從帳戶準備到用量策略設定,按步驟完成簡訊整合。

選擇下方語言按鈕即可切換到對應的在地化文件。

生產環境網域

控制台、前端代理與後端服務分別部署,請在網路策略中放行以下位址。

1. 準備帳戶

註冊並驗證信箱,控制台才會生成 API Key。

  1. 使用企業信箱註冊 可達SMS 帳戶。
  2. 完成信箱驗證碼驗證步驟。
  3. 登入控制台查看 API Key 與帳戶餘額。

2. 發送第一條簡訊

透過前端代理提交 AES-GCM 加密請求,系統會預凍結餘額並在通道接收後自動扣費。

POST https://www.kedasms.net/api/sms/send
Content-Type: application/json

{
  "apiKey": "your-api-key",
  "timestamp": 1712123456,
  "iv": "base64-iv==",
  "cipherText": "base64-ciphertext=="
}
{
  "code": "0",
  "message": "成功",
  "data": {
    "taskId": "20240408-0001",
    "message": "短信任务创建成功",
    "debitedAmount": 0.056,
    "currency": "USD"
  }
}

回應會返回任務 ID 以及本次扣費金額,便於核對。

3. 同步價格與覆蓋範圍

兩個介面均返回 JSON,可直接嵌入落地頁或內部看板。

4. 設定每日防護

透過每日簡訊和花費上限確保預算合規。

PUT https://backup.kedasms.net/api/external/accounts/limits
Content-Type: application/json

{
  "apiKey": "your-api-key",
  "dailySmsCap": 5000,
  "dailySpendCap": 200,
  "alertThreshold": 0.8,
  "blockOnLimit": true
}

該限制即時生效,API 與控制台任務都會遵循。

状态查询

查询短信发送状态,支持实时状态更新。

GET https://backup.kedasms.net/api/sms/status?msgid=msg_abc123def456

{
  "success": true,
  "data": {
    "msgid": "msg_abc123def456",
    "status": "DELIVERED",
    "recipient": "+8613800138000",
    "sender": "KedaSMS",
    "message": "您的验证码为 123456",
    "sent_time": "2024-01-01T12:00:00Z",
    "delivered_time": "2024-01-01T12:00:05Z"
  },
  "code": 200
}

回调通知

接收短信状态变更的实时通知,支持状态回调和链接点击回调。

POST https://backup.kedasms.net/api/sms/callback/KEDA
Content-Type: application/json

{
  "msgid": "msg_abc123def456",
  "status": "DELIVERED",
  "recipient": "+8613800138000",
  "sender": "KedaSMS",
  "sent_time": "2024-01-01T12:00:00Z",
  "delivered_time": "2024-01-01T12:00:05Z",
  "cost": "0.05"
}

短網址服務

使用相同的加密結構呼叫 /api/shortlinks,可產生可追蹤的專屬短網址。

POST https://www.kedasms.net/api/shortlinks
Content-Type: application/json

{
  "apiKey": "your-api-key",
  "timestamp": 1712123456,
  "iv": "base64-iv==",
  "cipherText": "base64-ciphertext=="
}

AES-256-GCM 加密範例

以下範例會先加密業務載荷並透過 HTTPS POST 到前端代理,同時將 timestamp 作為 AAD。

Python(PyCryptodome)

import base64
import json
import os
import time
import requests
from Crypto.Cipher import AES

api_key = "your-api-key"
encryption_key = base64.b64decode("YOUR_ENCRYPTION_KEY_BASE64")

payload = {
    "countryCode": "CN",
    "phoneNumbers": ["13800138000"],
    "content": "您的验证码为 123456",
    "senderId": "KedaSMS",
    "trackClick": True,
    "callbackUrl": "https://example.com/webhook/sms"
}

iv = os.urandom(12)
timestamp = int(time.time())

cipher = AES.new(encryption_key, AES.MODE_GCM, nonce=iv)
cipher.update(timestamp.to_bytes(8, "big"))
ciphertext, tag = cipher.encrypt_and_digest(json.dumps(payload).encode("utf-8"))

body = {
    "apiKey": api_key,
    "timestamp": timestamp,
    "iv": base64.b64encode(iv).decode(),
    "cipherText": base64.b64encode(ciphertext + tag).decode()
}

response = requests.post(
    "https://www.kedasms.net/api/sms/send",
    json=body,
    timeout=10
)
response.raise_for_status()
print(response.json())

Java(JDK 17 標準函式庫)

SecretKeySpec keySpec = new SecretKeySpec(Base64.getDecoder().decode("YOUR_ENCRYPTION_KEY_BASE64"), "AES");
byte[] iv = SecureRandom.getInstanceStrong().generateSeed(12);
long timestamp = Instant.now().getEpochSecond();

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, spec);
cipher.update(ByteBuffer.allocate(Long.BYTES).putLong(timestamp).array());

ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(payload);
byte[] encrypted = cipher.doFinal(json.getBytes(StandardCharsets.UTF_8));

Map<String, Object> body = Map.of(
        "apiKey", "your-api-key",
        "timestamp", timestamp,
        "iv", Base64.getEncoder().encodeToString(iv),
        "cipherText", Base64.getEncoder().encodeToString(encrypted)
);

String requestBody = objectMapper.writeValueAsString(body);
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://www.kedasms.net/api/sms/send"))
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(requestBody))
        .build();
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

Go(crypto/aes + cipher.GCM)

package main

import (
    "bytes"
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/base64"
    "encoding/binary"
    "encoding/json"
    "log"
    "net/http"
    "time"
)

func main() {
    apiKey := "your-api-key"
    payload := map[string]any{
        "countryCode": "CN",
        "phoneNumbers": []string{"13800138000"},
        "content": "您的验证码为 123456",
        "senderId": "KedaSMS",
        "trackClick": true,
        "callbackUrl": "https://example.com/webhook/sms",
    }

    key, err := base64.StdEncoding.DecodeString("YOUR_ENCRYPTION_KEY_BASE64")
    if err != nil {
        log.Fatal(err)
    }
    iv := make([]byte, 12)
    if _, err := rand.Read(iv); err != nil {
        log.Fatal(err)
    }

    block, err := aes.NewCipher(key)
    if err != nil {
        log.Fatal(err)
    }
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        log.Fatal(err)
    }

    timestamp := time.Now().Unix()
    aad := make([]byte, 8)
    binary.BigEndian.PutUint64(aad, uint64(timestamp))

    plaintext, err := json.Marshal(payload)
    if err != nil {
        log.Fatal(err)
    }
    ct := gcm.Seal(nil, iv, plaintext, aad)

    body := map[string]any{
        "apiKey":    apiKey,
        "timestamp": timestamp,
        "iv":        base64.StdEncoding.EncodeToString(iv),
        "cipherText": base64.StdEncoding.EncodeToString(ct),
    }

    bodyBytes, err := json.Marshal(body)
    if err != nil {
        log.Fatal(err)
    }

    req, err := http.NewRequest(http.MethodPost, "https://www.kedasms.net/api/sms/send", bytes.NewReader(bodyBytes))
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{Timeout: 10 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode >= 400 {
        log.Fatalf("unexpected status: %s", resp.Status)
    }
}

PHP(openssl + cURL)

<?php
$apiKey = 'your-api-key';
$encryptionKey = base64_decode('YOUR_ENCRYPTION_KEY_BASE64');

$payload = [
    'countryCode' => 'CN',
    'phoneNumbers' => ['13800138000'],
    'content' => '您的验证码为 123456',
    'senderId' => 'KedaSMS',
    'trackClick' => true,
    'callbackUrl' => 'https://example.com/webhook/sms',
];

$iv = random_bytes(12);
$timestamp = time();
$aad = pack('N2', $timestamp >> 32, $timestamp & 0xffffffff);

$ciphertext = openssl_encrypt(
    json_encode($payload, JSON_UNESCAPED_UNICODE),
    'aes-256-gcm',
    $encryptionKey,
    OPENSSL_RAW_DATA,
    $iv,
    $tag,
    $aad
);

if ($ciphertext === false) {
    throw new RuntimeException('encryption failed');
}

$body = [
    'apiKey' => $apiKey,
    'timestamp' => $timestamp,
    'iv' => base64_encode($iv),
    'cipherText' => base64_encode($ciphertext . $tag),
];

$ch = curl_init('https://www.kedasms.net/api/sms/send');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode($body),
    CURLOPT_TIMEOUT => 10,
]);

$response = curl_exec($ch);
if ($response === false) {
    throw new RuntimeException(curl_error($ch));
}

$httpStatus = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($httpStatus >= 400) {
    throw new RuntimeException('unexpected status: ' . $httpStatus);
}

echo $response;

請為每次請求產生新的 12 位元組 IV,並確保 timestamp 使用 UTC Unix 秒且與伺服器時間誤差不超過 5 分鐘。

5. 運維加固

按照以下最佳實踐保持通道穩定。

進入客戶控制台與 可達SMS 工程師溝通