Hướng Dẫn API SMSCode: Tích Hợp Số Ảo Vào Ứng Dụng Của Bạn

Hướng Dẫn API SMSCode: Tích Hợp Số Ảo Vào Ứng Dụng Của Bạn

TL;DR: API SMSCode cho phép tự động hóa quy trình mua số ảo và nhận OTP — chọn sản phẩm, đặt mua số, polling trạng thái, hủy khi server cho phép, và kiểm tra số dư. Mỗi create trả phí dùng một body cùng Idempotency-Key ổn định cho đến khi kết quả được xác định.

Nếu bạn là developer hoặc đang xây dựng hệ thống cần xác minh số điện thoại tự động ở quy mô lớn, API SMSCode là giải pháp cho phép tích hợp đầy đủ mà không cần thao tác thủ công.

Bài viết này hướng dẫn toàn diện cách sử dụng API — từ xác thực đến xử lý OTP hoàn chỉnh — với ví dụ code thực tế bằng Python, JavaScript, và cURL.

Tại Sao Cần API Số Ảo?

Khi bạn cần xác minh hàng chục hoặc hàng trăm tài khoản, làm thủ công qua dashboard web là không thực tế về mặt thời gian và chi phí nhân lực. API giải quyết vấn đề này bằng cách cho phép:

Tự động hóa hoàn toàn: Không cần con người can thiệp vào từng bước. Hệ thống tự mua số, chờ OTP, nhập OTP, và xử lý kết quả.

Tích hợp vào quy trình CI/CD: Automation testing với số điện thoại thực sự — test đăng ký người dùng, test luồng xác minh OTP, test toàn bộ onboarding flow.

Scale có kiểm soát: Giới hạn concurrency trong ứng dụng, tôn trọng 429/Retry-After, và theo dõi từng order riêng; API không tự quản lý tải thay cho client.

Xây dựng sản phẩm trên nền tảng số ảo: Dùng số ảo như một infrastructure layer trong ứng dụng của bạn — cung cấp dịch vụ xác minh cho người dùng của bạn.

Quản lý chi phí programmatically: Theo dõi chi tiêu, phân tích tỷ lệ thành công theo dịch vụ/quốc gia, tối ưu ngân sách dựa trên dữ liệu thực.

Bắt Đầu — Lấy API Key

  1. Đăng ký tài khoản SMSCode nếu chưa có — miễn phí, chỉ cần email
  2. Đăng nhập vào dashboard
  3. Vào Account > API để xem và copy API key
  4. Nạp tiền vào tài khoản trước khi bắt đầu test thực tế

Bảo mật API key:

  • Lưu vào biến môi trường (environment variable), không hardcode trong source code
  • Không commit vào git repository — thêm vào .gitignore
  • Không chia sẻ key trên Slack, email, hoặc bất kỳ kênh nào
  • Nếu key bị lộ, tạo key mới ngay trong phần Account

Base URL Và Xác Thực

Base URL: https://api.smscode.gg/v1

Tất cả requests cần header xác thực:

Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Ví dụ cURL kiểm tra kết nối:

curl -H "Authorization: Bearer YOUR_API_KEY" \
     https://api.smscode.gg/v1/balance

Các Endpoint Chính

Endpoint 1: Kiểm Tra Số Dư

GET /v1/balance

Response thành công:

{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 542000
  }
}

Dùng endpoint này để:

  • Kiểm tra trước khi đặt order để tránh lỗi insufficient balance
  • Monitoring số dư trong hệ thống tự động
  • Alert khi số dư xuống thấp

Endpoint 2: Xem Catalog — Số Khả Dụng

GET /v1/catalog/products?platform_id=1&country_id=7 HTTP/1.1
Authorization: Bearer YOUR_API_KEY

Parameters:

  • platform_id — ID nền tảng trong catalog
  • country_id — ID quốc gia trong catalog

Response:

{
  "success": true,
  "data": [
    {
      "id": 1024,
      "name": "WhatsApp - Indonesia",
      "catalog_product_id": 88,
      "country_id": 7,
      "platform_id": 1,
      "available": 142,
      "price": 5000,
      "active": true
    },
    {
      "id": 1025,
      "name": "Telegram - Indonesia",
      "catalog_product_id": 89,
      "country_id": 7,
      "platform_id": 2,
      "available": 58,
      "price": 7000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 1000, "count": 2 }
}

Kiểm tra available trước khi đặt order. Việc chọn sản phẩm phải hoàn tất trước create; không đổi sản phẩm sau một response create không rõ ràng.


Endpoint 3: Tạo Đơn Hàng — Mua Số

POST /v1/orders/create HTTP/1.1
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Idempotency-Key: order-example-001

{
  "catalog_product_id": 88,
  "quantity": 1
}

Response thành công:

{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 90210,
        "status": "ACTIVE",
        "phone_number": "+84987654321",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-03-16T10:20:00Z",
        "failed_reason": null,
        "product_id": 1024,
        "catalog_product_id": 88,
        "operator_id": null,
        "operator_name": null,
        "amount": 750000,
        "can_finish": false,
        "can_resend": false,
        "can_cancel": false,
        "can_replace": false,
        "can_reactivate": false,
        "resend_available_at": null,
        "cancel_available_at": "2026-03-16T10:02:00Z",
        "replace_available_at": "2026-03-16T10:02:00Z"
      }
    ],
    "failed_count": 0
  }
}

Ví dụ trên minh họa một đơn đã được gán số, nhưng phone_number là field optional/nullable cho đến khi việc gán hoàn tất. Sau khi nhận response:

  1. Lưu integer data.orders[0].id để poll sau.
  2. Chỉ dùng phone_number nếu đó là string không rỗng.
  3. Nếu field bị thiếu, là null hoặc trống, thực hiện tối đa một GET /v1/orders/{id} có timeout với đúng cùng id; nếu vẫn chưa được gán, dừng ở kết quả cục bộ pending_assignment mà không phát lại paid create.
  4. Chỉ sau khi có số hợp lệ mới yêu cầu ứng dụng đích gửi OTP và bắt đầu polling.

Endpoint 4: Polling OTP — Chờ Mã

GET /v1/orders/90210 HTTP/1.1
Authorization: Bearer YOUR_API_KEY

Projection V1OrderSummary khi đang chờ (chỉ các trường được chọn, không phải wire response đầy đủ):

{
  "success": true,
  "data": {
    "status": "ACTIVE",
    "otp_code": null,
    "otp_message": null,
    "sms_revision": 0,
    "can_cancel": true
  }
}

Projection V1OrderSummary khi OTP đến (chỉ các trường được chọn, không phải wire response đầy đủ):

{
  "success": true,
  "data": {
    "status": "OTP_RECEIVED",
    "otp_code": "847291",
    "otp_message": "Mã xác minh của bạn là 847291",
    "sms_revision": 1,
    "can_cancel": false
  }
}

Projection V1OrderSummary khi hết hạn (chỉ các trường được chọn, không phải wire response đầy đủ):

{
  "success": true,
  "data": {
    "status": "EXPIRED",
    "otp_code": null,
    "otp_message": null,
    "sms_revision": 1,
    "can_cancel": false
  }
}

otp_code có thể là null khi statusOTP_RECEIVED. Với từng order, khởi tạo last_seen_revision = -1; chỉ consume otp_message và cập nhật state khi sms_revision là integer tường minh, không phải boolean, lớn hơn nghiêm ngặt revision đã thấy và message là string không rỗng. Thực hiện trước khi xét COMPLETED, CANCELED hoặc EXPIRED, độc lập với OTP_RECEIVEDotp_code. Revision không hợp lệ, cũ/bằng hoặc message rỗng không làm state tiến lên; không tự suy luận kết quả tiền từ status.


Endpoint 5: Hủy Đơn Hàng

POST /v1/orders/cancel HTTP/1.1
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

{ "id": 90210 }

Response:

{
  "success": true,
  "data": {
    "order_id": 90210,
    "status": "CANCELED",
    "refund_amount": 750000,
    "new_balance": 2000000
  }
}

Chỉ gửi endpoint này sau khi snapshot mới nhất trả về can_cancel=true. Nếu can_cancel=false, tiếp tục đối soát trạng thái thay vì gửi một lệnh hủy dựa trên suy đoán phía client.


Ví Dụ Code Hoàn Chỉnh — Python

import requests
import time
import os
import uuid

API_KEY = os.environ.get("SMSCODE_API_KEY")  # Lấy từ env, không hardcode
BASE_URL = "https://api.smscode.gg/v1"
ORDER_TIMEOUT = (5, 30)
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json"
}


def check_balance():
    """Kiểm tra số dư tài khoản"""
    response = requests.get(f"{BASE_URL}/balance", headers=HEADERS)
    data = response.json()
    if data["success"]:
        return data["data"]["balance"]
    raise Exception(f"Lỗi kiểm tra số dư: {data['error']['message']}")


def get_available_products(platform_id: int, country_id: int):
    """Lấy danh sách số khả dụng"""
    response = requests.get(
        f"{BASE_URL}/catalog/products",
        headers=HEADERS,
        params={"platform_id": platform_id, "country_id": country_id}
    )
    data = response.json()
    if data["success"]:
        products = [
            p for p in data["data"]
            if p["available"] > 0 and type(p.get("catalog_product_id")) is int
        ]
        return sorted(products, key=lambda x: x["price"])  # Sắp xếp theo giá
    return []


def buy_number(catalog_product_id: int):
    """Mua số ảo và trả về thông tin đơn hàng"""
    request_body = {"catalog_product_id": catalog_product_id, "quantity": 1}
    idempotency_key = str(uuid.uuid4())
    create_headers = {**HEADERS, "Idempotency-Key": idempotency_key}
    response = requests.post(
        f"{BASE_URL}/orders/create",
        headers=create_headers,
        json=request_body,
        timeout=ORDER_TIMEOUT,
    )
    data = response.json()
    if not data["success"]:
        raise Exception(f"Không thể mua số: {data['error']['code']}")
    order = data["data"]["orders"][0]

    # `phone_number` là optional/nullable cho đến khi việc gán số hoàn tất. Đơn đã
    # resolved vẫn resolved: một lần đọc giới hạn với cùng id, không bao giờ tạo
    # thêm một create trả phí.
    if not is_assigned(order.get("phone_number")):
        current = requests.get(
            f"{BASE_URL}/orders/{order['id']}",
            headers=HEADERS,
            timeout=ORDER_TIMEOUT,
        ).json()
        if current.get("success"):
            order["phone_number"] = current["data"].get("phone_number")
    return order


def is_assigned(phone) -> bool:
    return isinstance(phone, str) and bool(phone.strip())


def wait_for_otp(order_id, timeout_seconds=120, poll_interval=5):
    """
    Poll OTP cho đến khi nhận được hoặc hết thời gian chờ.
    Returns: delivery dict nếu thành công, None nếu thất bại/hết hạn
    """
    start = time.time()
    last_seen_revision = -1
    while time.time() - start < timeout_seconds:
        response = requests.get(
            f"{BASE_URL}/orders/{order_id}",
            headers=HEADERS,
            timeout=ORDER_TIMEOUT,
        )
        data = response.json()["data"]

        status = data["status"]
        revision = data.get("sms_revision")
        message = data.get("otp_message")

        # OTP_RECEIVED và otp_code không phải điều kiện để consume message.
        if (
            type(revision) is int
            and revision > last_seen_revision
            and isinstance(message, str)
            and message.strip()
        ):
            last_seen_revision = revision
            return {
                "otp_code": data.get("otp_code"),
                "otp_message": message,
                "sms_revision": revision,
            }
        if status in ("COMPLETED", "EXPIRED", "CANCELED"):
            print(f"Phiên {order_id} đã kết thúc với status: {status}")
            return None

        print(f"  Đang chờ OTP... ({int(time.time() - start)}s)")
        time.sleep(poll_interval)

    print(f"Timeout sau {timeout_seconds}s")
    return None


def cancel_order(order_id):
    """Chỉ hủy đơn khi server cho phép"""
    current = requests.get(
        f"{BASE_URL}/orders/{order_id}",
        headers=HEADERS,
        timeout=ORDER_TIMEOUT,
    )
    current.raise_for_status()
    if not current.json()["data"]["can_cancel"]:
        return current.json()["data"]
    response = requests.post(
        f"{BASE_URL}/orders/cancel",
        headers=HEADERS,
        json={"id": order_id},
        timeout=ORDER_TIMEOUT,
    )
    return response.json()


def get_sms_for_service(platform_id: int, country_id: int):
    """
    Workflow một lần: chọn sản phẩm, tạo một đơn, rồi chờ tin nhắn.
    Returns: (phone_number, delivery) hoặc (None, None)
    """
    products = get_available_products(platform_id, country_id)
    if not products:
        print("Không có sản phẩm khả dụng")
        return None, None

    product = products[0]
    print(f"Mua sản phẩm catalog, giá Rp {product['price']:,}")
    order = buy_number(product["catalog_product_id"])
    order_id = order["id"]
    phone = order.get("phone_number")
    if not is_assigned(phone):
        # pending_assignment: đơn vẫn resolved và đã bị trừ tiền; đừng nhập số
        # vào ứng dụng nào và đừng tạo đơn mới. Trả về `order_id` để đối soát —
        # một tuple (None, None) sẽ làm mất id của đơn đã bị trừ tiền.
        print(f"pending_assignment: đơn {order_id} chưa được gán số")
        return {"kind": "pending_assignment", "order_id": order_id}, None

    print(f"Đã mua số: {phone}")
    print("  Nhập số này vào ứng dụng và yêu cầu OTP...")

    delivery = wait_for_otp(order_id)
    if delivery:
        print(f"  Tin nhắn nhận được: {delivery['otp_message']}")
        return phone, delivery

    cancel_order(order_id)
    return phone, None


# Sử dụng
if __name__ == "__main__":
    balance = check_balance()
    print(f"Số dư hiện tại: Rp {balance:,}")

    phone, delivery = get_sms_for_service(platform_id=1, country_id=7)
    if isinstance(phone, dict) and phone.get("kind") == "pending_assignment":
        # Đơn vẫn resolved và đã bị trừ tiền; giữ `order_id` để đối soát.
        print(f"\npending_assignment: đơn {phone['order_id']} chưa được gán số")
    elif phone and delivery:
        print(f"\nThành công!\nSố: {phone}\nTin nhắn: {delivery['otp_message']}")
    else:
        print("\nThất bại — xem log ở trên để debug")

Ví Dụ Code — JavaScript (Node.js)

const { Agent, fetch } = require('undici');

const API_KEY = process.env.SMSCODE_API_KEY;
const crypto = require('crypto');
const BASE_URL = 'https://api.smscode.gg/v1';
const orderDispatcher = new Agent({ connectTimeout: 5_000 });
const orderTransport = () => ({
  dispatcher: orderDispatcher,
  signal: AbortSignal.timeout(30_000)
});
const headers = {
  'Authorization': `Bearer ${API_KEY}`,
  'Content-Type': 'application/json'
};

async function checkBalance() {
  const res = await fetch(`${BASE_URL}/balance`, { headers });
  const { data } = await res.json();
  return data.balance;
}

async function getProducts(platformId, countryId) {
  const query = `?platform_id=${platformId}&country_id=${countryId}`;
  const res = await fetch(`${BASE_URL}/catalog/products${query}`, { headers });
  const { data } = await res.json();
  return data.filter(
    p => p.available > 0 && Number.isInteger(p.catalog_product_id)
  );
}

async function buyNumber(catalogProductId) {
  const requestBody = { catalog_product_id: catalogProductId, quantity: 1 };
  const idempotencyKey = crypto.randomUUID();
  const res = await fetch(`${BASE_URL}/orders/create`, {
    method: 'POST',
    headers: { ...headers, 'Idempotency-Key': idempotencyKey },
    body: JSON.stringify(requestBody),
    ...orderTransport()
  });
  const { data } = await res.json();
  const order = data.orders[0];

  // `phone_number` là optional/nullable cho đến khi việc gán số hoàn tất: một lần
  // đọc giới hạn với cùng id, không bao giờ tạo thêm một create trả phí.
  return order;
}

function isAssigned(phone) {
  return typeof phone === 'string' && phone.trim() !== '';
}

async function waitForOtp(orderId, timeoutMs = 120000, pollMs = 5000) {
  const deadline = Date.now() + timeoutMs;
  let lastSeenRevision = -1;

  while (Date.now() < deadline) {
    const res = await fetch(`${BASE_URL}/orders/${orderId}`, {
      headers,
      ...orderTransport()
    });
    const { data } = await res.json();

    const { status, sms_revision: revision, otp_message: message } = data;

    // OTP_RECEIVED và otp_code không phải điều kiện để consume message.
    if (
      Number.isInteger(revision) &&
      revision > lastSeenRevision &&
      typeof message === 'string' &&
      message.trim()
    ) {
      lastSeenRevision = revision;
      return {
        otpCode: data.otp_code,
        otpMessage: message,
        smsRevision: revision
      };
    }
    if (['COMPLETED', 'EXPIRED', 'CANCELED'].includes(status)) return null;

    await new Promise(r => setTimeout(r, pollMs));
  }
  return null;
}

async function cancelOrder(orderId) {
  const snapshot = await fetch(`${BASE_URL}/orders/${orderId}`, {
    headers,
    ...orderTransport()
  });
  const { data: current } = await snapshot.json();
  if (!current.can_cancel) return current;
  const res = await fetch(`${BASE_URL}/orders/cancel`, {
    method: 'POST',
    body: JSON.stringify({ id: orderId }),
    headers,
    ...orderTransport()
  });
  return res.json();
}

// Workflow một lần; không tạo đơn thứ hai khi kết quả create chưa rõ ràng.
async function getSmsForService(platformId, countryId) {
  const products = await getProducts(platformId, countryId);
  if (!products.length) {
    console.log('Không có sản phẩm khả dụng');
    return null;
  }

  const product = products.sort((a, b) => a.price - b.price)[0];
  const order = await buyNumber(product.catalog_product_id);
  if (!isAssigned(order.phone_number)) {
    // pending_assignment: đơn vẫn resolved và đã bị trừ tiền; đừng nhập số vào
    // đâu cả và đừng tạo đơn mới.
    console.log(`pending_assignment: đơn ${order.id} chưa được gán số`);
    return { kind: 'pending_assignment', orderId: order.id };
  }
  console.log(`Đã mua số: ${order.phone_number}`);

  const delivery = await waitForOtp(order.id);
  if (delivery) {
    console.log(`Tin nhắn nhận được: ${delivery.otpMessage}`);
    return { phone: order.phone_number, delivery };
  }
  await cancelOrder(order.id);
  return { phone: order.phone_number, delivery: null };
}

// Sử dụng
(async () => {
  const balance = await checkBalance();
  console.log(`Số dư: Rp ${balance}`);

  const result = await getSmsForService(1, 7);
  if (result && result.kind === 'pending_assignment') {
    // Đơn vẫn resolved và đã bị trừ tiền; giữ `orderId` để đối soát, đừng dùng số.
    console.log(`pending_assignment: đơn ${result.orderId} chưa được gán số`);
  } else if (result && result.delivery) {
    console.log(`\nThành công!\nSố: ${result.phone}\nTin nhắn: ${result.delivery.otpMessage}`);
  }
})();

Xử Lý Lỗi

API trả về lỗi theo format nhất quán:

{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "Số dư không đủ để thực hiện đơn hàng này"
  }
}

Bảng mã lỗi và cách xử lý:

Code Ý nghĩa Cách xử lý
INSUFFICIENT_BALANCE Số dư không đủ Nạp thêm tiền, hoặc check balance trước
VALIDATION_ERROR Body hoặc product ID không hợp lệ Kiểm tra catalog_product_id từ catalog
NO_OFFER_AVAILABLE Sản phẩm đã chọn không còn offer Dừng request này; lựa chọn mới phải là thao tác riêng
CONFLICT Request xung đột với trạng thái hiện tại Đọc lại trạng thái order
NOT_FOUND Order ID không tồn tại Kiểm tra lại order ID
RATE_LIMIT_EXCEEDED Quá nhiều requests Giảm tần suất poll, thêm delay
UNAUTHORIZED API key không hợp lệ Kiểm tra API key trong Account
SERVICE_UNAVAILABLE Dịch vụ tạm thời không khả dụng Dừng create và đối soát trước khi thử lại

Best Practices Cho Production

Polling thông minh — không poll quá dày: Chọn khoảng poll và timeout tổng có giới hạn trong cấu hình ứng dụng. Khi nhận 429, dùng Retry-After dương; nếu header thiếu hoặc không hợp lệ, dùng một khoảng chờ dự phòng có giới hạn.

Create fail-closed: Chỉ NO_OFFER_AVAILABLE, VALIDATION_ERROR, PROVIDER_ERROR, và IDEMPOTENCY_KEY_REUSED là kết quả create dứt khoát cho phép quy trình hiện tại kết thúc. REQUEST_IN_PROGRESS chỉ được retry có giới hạn với đúng body và Idempotency-Key; INSUFFICIENT_BALANCE dừng sau một POST. Với mã khác, mã tương lai, JSON hỏng hoặc UTF-8 không hợp lệ, lưu endpoint, body, key và số lần thử để đối soát, không mua số mới.

Kiểm tra số dư trước khi batch: Trước khi bắt đầu batch lớn (10+ orders), check balance để đảm bảo đủ tiền. Dừng sớm khi số dư thấp hơn threshold.

MIN_BALANCE = 100000  # Ngưỡng IDR tối thiểu
if check_balance() < MIN_BALANCE:
    raise Exception("Số dư quá thấp, hãy nạp thêm tiền")

Logging đầy đủ: Log ID order, catalog_product_id, kết quả và thời gian. Không log API key, OTP hoặc toàn bộ nội dung SMS.

Không hardcode catalog_product_id: Product ID có thể thay đổi. Luôn gọi catalog API để lấy catalog_product_id hiện tại thay vì hardcode.

Xử lý concurrent orders: Nếu cần nhiều số cùng lúc, dùng async/await hoặc threading nhưng vẫn giới hạn concurrency trong ứng dụng và phản ứng với 429; không giả định quota orders/phút cố định.

Health check định kỳ: Trong production, chạy health check mỗi giờ: kiểm tra balance, kiểm tra catalog có số khả dụng không, alert nếu có vấn đề.


Use Cases Thực Tế

Automation Testing: Tích hợp với Selenium, Playwright, hoặc Cypress để test toàn bộ luồng đăng ký người dùng bao gồm OTP verification. Môi trường staging cần tài khoản thật, API SMSCode cho phép điều này một cách programmatic.

# Ví dụ với Playwright
from playwright.sync_api import sync_playwright
from smscode_client import get_sms_for_service  # Hàm một lần ở trên

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto("https://app-to-test.com/register")

    # Lấy số ảo
    phone, delivery = get_sms_for_service(platform_id=1, country_id=7)

    # `phone` có thể là kết quả pending_assignment: đơn vẫn resolved và đã bị trừ
    # tiền, nên đừng điền gì vào form và giữ lại `order_id` để đối soát.
    if isinstance(phone, dict) and phone.get("kind") == "pending_assignment":
        print(f"pending_assignment: đơn {phone['order_id']} chưa được gán số")
        browser.close()
        raise SystemExit(0)

    # Đừng dựa vào `assert delivery is not None` để bảo vệ ô nhập số: đó là một
    # biến khác. Kiểm tra chính giá trị sẽ được điền vào form.
    if not (isinstance(phone, str) and phone.strip()):
        print("Không có số khả dụng — không điền gì vào form")
        browser.close()
        raise SystemExit(0)

    assert delivery is not None

    # Điền vào form
    page.fill("#phone-input", phone)
    page.click("#send-otp-btn")

    # OTP đã có sẵn
    page.fill("#otp-input", delivery["otp_code"] or delivery["otp_message"])
    page.click("#verify-btn")

    assert page.url.endswith("/dashboard")

Bulk Account Verification: Với workflow đã được phép, API có thể xử lý nhiều order khi client tự giới hạn concurrency, theo dõi idempotency và phản ứng đúng với 429; rate limit không được quản lý tự động thay cho client.

SaaS Product — Cung Cấp Xác Minh Cho Khách Hàng: Nếu bạn xây dựng sản phẩm yêu cầu xác minh số điện thoại, API SMSCode cho phép bạn cung cấp dịch vụ này mà không cần đầu tư vào hạ tầng SMS. Bạn là khách hàng của SMSCode, khách hàng của bạn là người dùng cuối.


FAQ

API có hỗ trợ webhook không?

Có. Đọc raw request bytes và kiểm tra X-Webhook-Signature theo định dạng sha256={hex} bằng so sánh constant-time trước khi parse JSON. Sau khi xác minh, xử lý mọi nhánh sự kiện, persist hoặc enqueue bền vững, rồi mới trả 2xx. Polling GET /v1/orders/{id} vẫn là cơ chế đối soát khi webhook đến chậm hoặc bị mất.

Có thể dùng API cho automation testing không?

Có — đây là một trong những use case phổ biến và được hỗ trợ tốt nhất. Tích hợp tốt với Selenium, Playwright, Puppeteer, Cypress. API response time thấp phù hợp với test runner có timeout chặt.

Nên giới hạn số lượng đơn hàng đồng thời thế nào?

Đặt giới hạn concurrency theo workload của bạn. Khi API trả về 429, tôn trọng Retry-After dương hoặc dùng fallback có giới hạn; không hardcode một quota chung cho mọi tài khoản.

Làm thế nào để test API mà không tốn nhiều tiền?

Dùng sản phẩm đang hoạt động có giá IDR thấp trong catalog để kiểm tra logic trước. Sau mỗi lần tạo đơn có tính phí, hãy đối soát trạng thái trước khi gửi một yêu cầu tạo đơn mới.

API có tài liệu Swagger/OpenAPI không?

Có — truy cập tài liệu API để xem endpoint, schema và ví dụ request/response. Thực thi request bằng HTTP client phía server của bạn; trang tài liệu không phải công cụ gửi request trực tiếp từ trình duyệt.

Có SDK chính thức không?

API REST có thể được gọi trực tiếp từ mọi ngôn ngữ bằng HTTP client chuẩn. Các ví dụ trong bài này minh họa hợp đồng HTTP và không giả định có SDK chính thức.


Sẵn sàng tích hợp? Đăng ký SMSCode và lấy API key miễn phí ngay hôm nay. Tham khảo thêm số ảo cho kinh doanh và developer để khám phá toàn bộ capabilities, hoặc xem hướng dẫn nạp tiền để cấu hình tài khoản cho production.

Sẵn sàng thử SMSCode?

Tạo tài khoản và nhận số ảo đầu tiên trong chưa đầy hai phút.

Bắt đầu →