SMSCode API Kullanımı: Geliştirici Entegrasyon Rehberi (2026)

SMSCode API Kullanımı: Geliştirici Entegrasyon Rehberi (2026)

Kısa özet: SMSCode API, katalogdaki gerçek bir catalog_product_id ile ücretli sipariş oluşturur. Her mantıksal sipariş için tek bir Idempotency-Key ve tek bir istek gövdesi kullanın. Belirsiz bir ağ veya sunucu sonucunda aynı anahtar ile aynı gövdeyi yeniden gönderin; yeni bir sipariş oluşturmayın.

API erişimi

API anahtarınızı SMSCode hesabınızdan alın ve yalnızca sunucu tarafında saklayın. Anahtarı tarayıcı koduna, mobil uygulamaya, URL query parametresine veya loglara yazmayın.

https://api.smscode.gg/v1

Her istekte Bearer kimlik doğrulaması kullanılır:

Authorization: Bearer YOUR_API_TOKEN

Temel akış

  1. /catalog/products üzerinden sipariş verilebilir ürünü bulun.
  2. Bir mantıksal istek için gövdeyi ve Idempotency-Key değerini bir kez oluşturun.
  3. /orders/create ile siparişi oluşturun.
  4. /orders/{id} ile durumu sorgulayın veya imzalı webhook olaylarını işleyin.
  5. Yalnızca can_cancel: true olduğunda /orders/cancel çağrısını yapın.

Katalogdan ürün seçme

Ürünler ülke ve platform kimlikleriyle filtrelenir. Yanıttaki catalog_product_id, yönlendirilmiş sipariş oluşturma için kullanılır.

GET /v1/catalog/products?country_id=7&platform_id=1&limit=100 HTTP/1.1
Host: api.smscode.gg
Authorization: Bearer YOUR_API_TOKEN
{
  "success": true,
  "data": [
    {
      "id": 1024,
      "name": "WhatsApp - Indonesia",
      "country_id": 7,
      "platform_id": 1,
      "available": 142,
      "price": 750000,
      "active": true,
      "catalog_product_id": 88
    }
  ],
  "meta": {
    "page": 1,
    "limit": 100,
    "count": 1
  }
}

price IDR cinsinden tam sayıdır. Stok anlık değişebilir; katalogda görünen bir ürün sipariş anında yine de NO_OFFER_AVAILABLE döndürebilir.

Güvenli bir sipariş oluşturma

Aşağıdaki örnek tek seferlik bir oluşturmadır. Aynı mantıksal isteği ağ hatasından sonra yeniden denerseniz hem Idempotency-Key hem de JSON gövdesi byte düzeyinde aynı kalmalıdır.

POST /v1/orders/create HTTP/1.1
Host: api.smscode.gg
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
Idempotency-Key: logical-request-7f36f7cb

{"catalog_product_id":88,"quantity":1}
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 90210,
        "status": "ACTIVE",
        "phone_number": "+6281234567890",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-05-11T09:20:00+00:00",
        "failed_reason": null,
        "product_id": 1024,
        "catalog_product_id": 88,
        "operator_id": 42,
        "operator_name": "Telkomsel",
        "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-05-11T09:02:00+00:00",
        "replace_available_at": "2026-05-11T09:02:00+00:00"
      }
    ],
    "failed_count": 0
  }
}

Bu örnekte phone_number atanmıştır; alan atama tamamlanana kadar opsiyonel/nullable olabilir. Çözümlenmiş siparişte değer eksik, null veya boşsa aynı tamsayı id için en fazla bir timeout’lu GET /v1/orders/{id} yapın. Değer hâlâ boş olmayan bir string değilse ücretli create’i tekrarlamadan yerel pending_assignment sonucunda durun ve numarayı hedef uygulamaya göndermeyin.

cURL örneğinde anahtar ve gövde ayrı değişkenlerde bir kez oluşturulur:

IDEMPOTENCY_KEY="$(uuidgen)"
REQUEST_BODY='{"catalog_product_id":88,"quantity":1}'

curl --fail-with-body --connect-timeout 5 --max-time 30 --request POST "https://api.smscode.gg/v1/orders/create" \
  --header "Authorization: Bearer $SMSCODE_API_TOKEN" \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: $IDEMPOTENCY_KEY" \
  --data-binary "$REQUEST_BODY"

Sipariş durumunu ve SMS teslimini okuma

Geçerli durumlar yalnızca ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED ve EXPIRED değerleridir. Her sipariş görünümü için revision durumunu -1 ile başlatın ve render çağrıları arasında sipariş kimliğine göre saklayın. Boolean olmayan açık bir tamsayı sms_revision yalnızca önceki değerden kesin olarak büyük ve otp_message boş olmayan bir string ise mesajı tüketin ve durumu ilerletin. Bu karar OTP_RECEIVED veya otp_code alanına bağlı değildir ve terminal durum kontrolünden önce verilir; geçersiz, eski/eşit revision ya da boş mesaj revision durumunu ilerletmez.

GET /v1/orders/90210 HTTP/1.1
Host: api.smscode.gg
Authorization: Bearer YOUR_API_TOKEN

V1OrderSummary projeksiyonu (seçili alanlar; tam wire response değildir):

{
  "success": true,
  "data": {
    "id": 90210,
    "status": "OTP_RECEIVED",
    "otp_code": null,
    "otp_message": "Giriş bağlantınız: https://example.com/confirm",
    "sms_revision": 1,
    "otp_received_at": "2026-05-11T09:01:30+00:00",
    "can_cancel": false
  }
}

Aşağıdaki iki fonksiyon polling sonucunu ve iptal kararını API alanlarından üretir:

def classify_order_snapshot(order: dict) -> dict:
    status = order["status"]
    order_id = order["id"]
    revision_by_order = getattr(
        classify_order_snapshot,
        "_revision_by_order",
        {},
    )
    classify_order_snapshot._revision_by_order = revision_by_order

    last_seen_revision = -1
    if order_id in revision_by_order:
        last_seen_revision = revision_by_order[order_id]

    revision = order.get("sms_revision")
    message = order.get("otp_message")

    # OTP_RECEIVED ve otp_code mesaj tüketimi için kapı değildir.
    if (
        type(revision) is int
        and revision > last_seen_revision
        and isinstance(message, str)
        and message.strip()
    ):
        revision_by_order[order_id] = revision
        return {
            "outcome": "delivered",
            "otp_code": order.get("otp_code"),
            "otp_message": message,
            "sms_revision": revision,
        }
    if status == "COMPLETED":
        return {"outcome": "completed"}
    if status == "CANCELED":
        return {"outcome": "canceled"}
    if status == "EXPIRED":
        return {"outcome": "expired"}
    return {"outcome": "active"}


def cancellation_decision(order: dict) -> str:
    return "cancel" if order["can_cancel"] else "wait"

COMPLETED, teslim sinyali değildir. Bir terminal snapshot hâlâ tüketilmemiş daha yeni bir SMS taşıyabilir; bu nedenle revision tüketimi her zaman COMPLETED, CANCELED ve EXPIRED dallarından önce gelir.

Siparişi iptal etme

İptal için önce en güncel sipariş görünümündeki can_cancel değerini kontrol edin. SMS teslim edilmişse iptal ve iade uygunluğu kapanır.

POST /v1/orders/cancel HTTP/1.1
Host: api.smscode.gg
Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json

{"id":90210}

CANCEL_TOO_EARLY minimum bekleme süresinin dolmadığını, CONFLICT siparişin iptal edilebilir durumda olmadığını belirtir. Bu sonuçlarda yeni bir sipariş oluşturarak hatayı gizlemeyin.

Hata sınıflandırması ve yeniden deneme

İyi biçimlenmiş hata gövdesindeki error.code alanına göre karar verin; serbest biçimli message metnine göre branch etmeyin.

Kod Oluşturma davranışı
NO_OFFER_AVAILABLE Kesin iş reddi; farklı ürün/ülke için yeni gövde ve yeni anahtar kullanılabilir.
VALIDATION_ERROR Kesin iş reddi; isteği düzeltmeden yeniden göndermeyin.
PROVIDER_ERROR Kesin iş reddi; alternatif seçilecekse yeni mantıksal istek oluşturun.
IDEMPOTENCY_KEY_REUSED Kesin iş reddi; aynı anahtar farklı gövdeyle kullanılmıştır.
INSUFFICIENT_BALANCE İlk ücretli POST’tan sonra durun; failover yapmayın.
REQUEST_IN_PROGRESS Kısa ve sınırlı beklemeden sonra aynı anahtar ve aynı gövdeyle deneyin.

Diğer bilinen kodların tamamı ile gelecekte eklenecek bilinmeyen kodlar belirsiz sonuç olarak kapanır: yeni ülkeye geçmeyin, gövdeyi veya anahtarı değiştirmeyin. Bozuk JSON, kesilmiş yanıt ve geçersiz UTF-8 bir iş hatası değildir; mevcut anahtar, tam gövde, ülke ve tüm deneme geçmişiyle belirsiz sonucu kaydedin.

Retry-After yoksa veya pozitif bir tamsayı olarak ayrıştırılamıyorsa sınırlı varsayılan gecikme kullanın. Buradaki 60 saniyelik üst sınır istemciye ait yerel bir politikadır; API kotası veya SLA değildir:

def retry_after_seconds(headers: dict, fallback: int = 2) -> int:
    try:
        fallback_seconds = int(fallback)
    except (TypeError, ValueError, OverflowError):
        fallback_seconds = 2
    bounded_fallback = max(1, min(fallback_seconds, 60))

    raw_value = headers.get("Retry-After")
    try:
        seconds = int(raw_value)
    except (TypeError, ValueError, OverflowError):
        return bounded_fallback
    if seconds <= 0:
        return bounded_fallback
    return max(1, min(seconds, 60))

Webhook imzası ve işleme sırası

Webhook secret ayarlıysa X-Webhook-Signature değeri sha256={hex} biçimindedir. İmzayı parse edilmiş JSON üzerinden değil, gelen ham request byte’ları üzerinden doğrulayın. Sıra şu olmalıdır: doğrula, parse et, normalize et, atomik olarak kalıcı inbox’a yaz, sonra 2xx döndür; iş etkilerini ayrı bir worker çalıştırır.

import hashlib
import hmac
import json
import os

from flask import Flask, jsonify, request
from myapp.inbox import durable_inbox


app = Flask(__name__)
WEBHOOK_SECRET = os.environ["SMSCODE_WEBHOOK_SECRET"].encode("utf-8")


def verify_signature(raw_body: bytes, signature: str) -> bool:
    if not signature.startswith("sha256="):
        return False
    try:
        provided = bytes.fromhex(signature.removeprefix("sha256="))
    except ValueError:
        return False
    expected = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).digest()
    return hmac.compare_digest(expected, provided)


def normalize_event(event: dict) -> dict:
    data = event["data"]
    if event["event"] == "webhook.test":
        return {"event": event["event"], "message": data["message"]}
    return {
        "event": event["event"],
        "order_id": data["order_id"],
        "otp_message": data["otp_message"],
        "sms_revision": data["sms_revision"],
    }


def webhook_dedupe_key(event: dict, raw_body: bytes) -> str:
    data = event["data"]
    if event["event"] == "webhook.test":
        return f'{event["event"]}:{hashlib.sha256(raw_body).hexdigest()}'
    if event["event"] == "order.otp_received":
        return f'{event["event"]}:{data["order_id"]}:{data["sms_revision"]}'
    return f'{event["event"]}:{data["order_id"]}'


def persist_or_enqueue_once(dedupe_key: str, event_record: dict) -> None:
    durable_inbox.insert_if_absent(dedupe_key, event_record)


@app.post("/webhooks/smscode")
def smscode_webhook():
    raw_body = request.get_data(cache=True, as_text=False)
    signature = request.headers.get("X-Webhook-Signature", "")
    if not verify_signature(raw_body, signature):
        return jsonify({"error": "invalid signature"}), 401

    event = json.loads(raw_body)
    dedupe_key = webhook_dedupe_key(event, raw_body)
    event_record = normalize_event(event)
    persist_or_enqueue_once(dedupe_key, event_record)
    return jsonify({"status": "accepted"}), 202

Olası sipariş olayları order.otp_received, order.completed, order.expired ve order.canceled değerleridir. insert_if_absent çağrısı unique anahtar üzerinde atomik olmalıdır: OTP olayı için event + order_id + sms_revision, terminal olay için event + order_id, webhook.test için ham gövdenin SHA-256 değeri kullanılır. Request handler yalnızca olayı normalize edip kalıcı inbox’a yazar; iş etkilerini inbox’ı tüketen ayrı bir worker’da çalıştırın. Kalıcı kayıt veya kuyruklama başarısızsa 2xx döndürmeyin.

Güvenlik kontrol listesi

  • API ve webhook secret değerlerini environment secret store içinde tutun.
  • API anahtarını URL’ye, istemci tarafı koda veya loga koymayın.
  • Her HTTP çağrısına bağlantı ve toplam timeout uygulayın.
  • Ücretli create sonucunda belirsizlik varsa yeni sipariş oluşturmayın.
  • Idempotency-Key, tam request gövdesi ve deneme geçmişini birlikte saklayın.
  • Sipariş kimliğini ve sms_revision değerini loglayın; token, telefon ve mesaj metnini loglamayın.

FAQ

Resmî SDK gerekli mi?

Hayır. API standart HTTPS ve JSON kullanır. HTTP istemcisi olan her dil kullanılabilir; bu rehberdeki sözleşmeleri kendi istemcinizde korumanız gerekir.

otp_code null ise SMS gelmedi mi?

Hayır. Teslimatı status veya otp_code alanına bağlamayın. Sipariş için tutulan revision değerinden kesin olarak büyük, boolean olmayan bir tamsayı sms_revision ile boş olmayan string otp_message birlikteyse mesajı terminal durum kontrolünden önce tüketip revision durumunu ilerletin. otp_code bu sırada null olabilir.

Timeout sonrası hemen iptal etmeli miyim?

Hayır. Önce siparişi tekrar okuyun ve yalnızca can_cancel: true olduğunda iptal çağrısı yapın. Belirsiz create yanıtını iptal edilecek bir sipariş yokmuş gibi yorumlamayın.


Tam endpoint ve schema sözleşmesi için API dokümantasyonunu kullanın.

İlgili rehberler:

SMSCode'i denemeye hazır mısınız?

Hesap oluşturun ve ilk sanal numaranızı iki dakikadan kısa sürede alın.

Başlayın →