SMS API Entegrasyonu: Geliştiriciler İçin Adım Adım Rehber
OTP'den sipariş bildirimlerine kadar SMS API entegrasyonunun her adımını ele alıyoruz: API anahtarı yönetimi, idempotency, HMAC imzalı webhook'lar, retry/backoff stratejisi ve güvenlik uygulamaları. Node.js ve Python kod örnekleriyle.
Bir e-ticaret sitesinde kullanıcıya sipariş onayı göndermek, bankacılık uygulamasında OTP iletmek ya da klinik randevu sisteminde hatırlatma mesajı çıkarmak: hepsinin arkasında aynı mekanizma var — SMS API entegrasyonu. Ancak bu entegrasyonu doğru yapmak, yalnızca birkaç HTTP isteği göndermekten ibaret değil. İdempotency, rate limit yönetimi, webhook güvenliği ve sır saklama gibi mühendislik konuları, SMS API'nin canlı ortamda güvenilir çalışmasını belirliyor.
Bu yazıda İletiniz API'sini örnek alarak SMS entegrasyonunun her adımını ele alacağız. Teknik tavsiyelerin büyük bölümü sağlayıcıdan bağımsız; ister Twilio, ister Vonage, ister İletiniz kullanıyor olun benzer prensipler geçerli.
SMS API Nedir ve Ne İşe Yarar?
SMS API, uygulamanızın bir mesajlaşma altyapısına HTTP istekleri göndererek SMS iletmesini sağlayan bir arayüzdür. Operatör ağlarıyla doğrudan bağlantı kurmak yerine, sağlayıcının API'sini çağırırsınız; sağlayıcı teslim garantisi, raporlama ve webhook bildirimlerini yönetir.
Yaygın kullanım senaryoları şunlardır:
- OTP / Tek kullanımlık şifre: Giriş, ödeme onayı veya kimlik doğrulama akışlarında 4-6 haneli kod iletimi.
- Sipariş ve kargo bildirimi: "Siparişiniz kargoya verildi" gibi işlemsel (transactional) mesajlar.
- Randevu hatırlatma: Klinik, güzellik salonu veya etkinlik sistemlerinde müşteri kaybını azaltır.
- Pazarlama / kampanya: Kitlesel duyurular, indirim bildirimleri — KVKK uyumlu izinli pazarlama.
- Alarm ve uyarı: Sunucu hataları, stok tükenmesi gibi operasyonel bildirimler.
REST API Mantığı: Temel Kavramlar
Modern SMS API'lerin büyük çoğunluğu REST mimarisini izler: JSON gövdeli HTTP istekleri, standart durum kodları ve Bearer token kimlik doğrulaması. İletiniz API'si de bu modeli kullanır.
Bir SMS göndermek için asgari üç bilgiye ihtiyacınız var:
- API anahtarı (kimlik doğrulama için)
- Alıcı telefon numarası (E.164 formatında, örn.
+905321234567) - Mesaj içeriği
Bazı sağlayıcılar ek olarak gönderici kimliği (sender ID / başlık), mesaj şablonu referansı veya zamanlama parametresi de alır.
API Anahtarı Oluşturma ve Güvenli Saklama
Live vs Test Anahtarı
İletiniz, iki farklı anahtar türü üretir:
iltz_live_...— gerçek SMS gönderir, ücretlendirilir.iltz_test_...— gerçek SMS göndermez; sandbox ortamında yanıt simüle eder.
API paneline giriş yaptıktan sonra Ayarlar → API Anahtarları bölümünden yeni anahtar oluşturulur. Önemli kural: düz metin anahtar yalnızca bir kez gösterilir. Oluşturduktan hemen sonra güvenli bir yerde saklayın; panel bu değeri bir daha göstermez, yalnızca son 4 karakteri görüntüler.
Ortam Değişkenleri ile Saklama
API anahtarını kaynak koduna gömmek en sık yapılan ve en tehlikeli hatadır. Doğru yaklaşım ortam değişkenleri kullanmaktır:
ILETINIZ_API_KEY=iltz_live_xxxxxxxxxxxxxxxxxxxx
ILETINIZ_API_URL=https://api.iletiniz.com/v1
Production ortamında .env dosyası yerine bir sır yönetim sistemi kullanın: AWS Secrets Manager, HashiCorp Vault, Doppler veya GitHub Actions Secrets bunların başında gelir. Anahtarlar versiyon kontrol sistemine (Git) asla commit edilmemeli; .gitignore'a .env eklemek zorunludur.
⚠️ Dikkat: API anahtarınız yanlışlıkla public bir repo'ya gittiyse, hemen iptal edip yenisini oluşturun. Git geçmişinden silmek yeterli değildir; GitHub Secret Scanning gibi araçlar anahtarı zaten taramış olabilir.
İlk SMS İsteğini Göndermek
cURL ile Hızlı Test
curl -X POST https://api.iletiniz.com/v1/messages \
-H "Authorization: Bearer $ILETINIZ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+905321234567",
"message": "Merhaba! Siparişiniz #12345 kargoya verildi.",
"sender": "MAGARAIM"
}'
Başarılı bir yanıt şu yapıda gelir:
{
"id": "msg_01HXXXXXXXXXXXXXXXXXXXXX",
"status": "queued",
"to": "+905321234567",
"sender": "MAGARAIM",
"created_at": "2026-05-16T10:23:00Z"
}
Node.js ile Entegrasyon (fetch API)
// sms.js
const ILETINIZ_API_KEY = process.env.ILETINIZ_API_KEY;
const ILETINIZ_API_URL = process.env.ILETINIZ_API_URL ?? "https://api.iletiniz.com/v1";
/**
* SMS gönderir; idempotency-key ile çift gönderim önlenir.
* @param {string} to - E.164 formatında alıcı (+905321234567)
* @param {string} message - Mesaj metni
* @param {string} [idempotencyKey] - UUID v4 önerilir
*/
async function sendSms(to, message, idempotencyKey) {
const headers = {
"Authorization": `Bearer ${ILETINIZ_API_KEY}`,
"Content-Type": "application/json",
};
if (idempotencyKey) {
headers["Idempotency-Key"] = idempotencyKey;
}
const response = await fetch(`${ILETINIZ_API_URL}/messages`, {
method: "POST",
headers,
body: JSON.stringify({
to,
message,
sender: "MAGARAIM",
}),
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(`SMS gönderilemedi [${response.status}]: ${error.message ?? "Bilinmeyen hata"}`);
}
return response.json();
}
// Kullanım
const { randomUUID } = await import("crypto");
const result = await sendSms(
"+905321234567",
"Doğrulama kodunuz: 847291. Kod 5 dakika geçerlidir.",
randomUUID()
);
console.log("Gönderildi:", result.id);
Python ile Entegrasyon (requests)
# sms.py
import os
import uuid
import requests
ILETINIZ_API_KEY = os.environ["ILETINIZ_API_KEY"]
ILETINIZ_API_URL = os.environ.get("ILETINIZ_API_URL", "https://api.iletiniz.com/v1")
def send_sms(to: str, message: str, idempotency_key: str | None = None) -> dict:
"""
SMS gönderir.
Args:
to: E.164 formatında alıcı numarası, örn. "+905321234567"
message: Mesaj metni
idempotency_key: Çift gönderimi önlemek için benzersiz anahtar
Returns:
API yanıtı (dict)
Raises:
requests.HTTPError: API hata döndürürse
"""
headers = {
"Authorization": f"Bearer {ILETINIZ_API_KEY}",
"Content-Type": "application/json",
}
if idempotency_key:
headers["Idempotency-Key"] = idempotency_key
payload = {
"to": to,
"message": message,
"sender": "MAGARAIM",
}
response = requests.post(
f"{ILETINIZ_API_URL}/messages",
json=payload,
headers=headers,
timeout=10,
)
response.raise_for_status()
return response.json()
if __name__ == "__main__":
result = send_sms(
to="+905321234567",
message="Siparişiniz hazırlandı! Takip linki: https://takip.example.com/12345",
idempotency_key=str(uuid.uuid4()),
)
print(f"Gönderildi: {result['id']}")
Idempotency-Key ile Çift Gönderim Önleme
Ağ kesintileri veya zaman aşımları nedeniyle aynı isteği birden fazla kez gönderebilirsiniz. Kullanıcı iki OTP alır ya da aynı sipariş bildirimi iki kez iletilir — bu hem kötü bir deneyimdir hem de ek maliyete yol açar.
Idempotency-Key, aynı isteğin tekrar gönderildiğinde API'nin ikinci bir SMS üretmek yerine ilk yanıtı döndürmesini sağlar. Kural basittir: her iş birimi için UUID v4 ile benzersiz bir anahtar üretin ve bu anahtarı o iş biriminin tüm retry denemelerinde kullanın.
İyi uygulama: Idempotency anahtarını veritabanınızda ilgili kayıtla (sipariş ID, kullanıcı ID + işlem türü) birlikte saklayın. Böylece uygulama yeniden başlasa bile aynı anahtarı yeniden oluşturabilirsiniz. Stripe, Adyen gibi ödeme sistemleri bu yaklaşımı zorunlu kılar.
Rate Limit ve Retry / Backoff Stratejisi
Çoğu SMS API'si saniyede veya dakikada gönderilebilecek mesaj sayısını kısıtlar. Limiti aşınca API 429 Too Many Requests döndürür. Bu noktada yapılacak en yaygın hata: döngü içinde art arda istek atmaya devam etmek.
Doğru yaklaşım üstel geri çekilme (exponential backoff) ile yeniden denemektir:
async function sendSmsWithRetry(to, message, options = {}) {
const { maxRetries = 4, baseDelay = 500 } = options;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await sendSms(to, message, options.idempotencyKey);
} catch (err) {
const isRetryable =
err.message.includes("429") || // Rate limit
err.message.includes("503") || // Service unavailable
err.message.includes("502"); // Bad gateway
if (!isRetryable || attempt === maxRetries) throw err;
// Jitter ekleyerek "thundering herd" sorununu önle
const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 200;
console.warn(`Deneme ${attempt + 1} başarısız, ${Math.round(delay)}ms bekleniyor...`);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
Yüksek hacimli gönderimler için (toplu kampanya, toplu OTP) SMS API çağrılarını doğrudan uygulama thread'inden değil, bir mesaj kuyruğu (RabbitMQ, Redis Streams, Amazon SQS) üzerinden yapın. Bu yaklaşım hem rate limit'i yönetir hem de uygulama yeniden başlarken mesajların kaybolmasını önler.
Webhook ile Teslim Durumu Takibi
SMS API'si mesajı kuyruğa aldığında size yalnızca bir message_id döner. Gerçek teslim durumunu öğrenmek için iki yöntem var: polling (sürekli sorgu) veya webhook. Polling kaynak israfına yol açar; production'da her zaman webhook tercih edilmeli.
İletiniz Webhooks 2.0, teslim durum değişikliklerini (delivered, failed, rejected vb.) HMAC imzalı HTTP POST istekleri olarak belirttiğiniz endpoint'e iletir. En-az-bir-kez (at-least-once) teslim garantisi sağlar; yani aynı olayı birden fazla kez alabilirsiniz. Bu nedenle webhook handler'ınızı idempotent yazmalısınız.
HMAC İmza Doğrulama (Node.js)
Her webhook isteğinin başlığında X-Iletiniz-Signature alanı gelir. Bu değer, webhook secret ile istek gövdesinden türetilmiş bir HMAC-SHA256 özetidir. İmzayı doğrulamadan işlem yaparsanız sahte istek riski oluşur.
import express from "express";
import crypto from "crypto";
const app = express();
const WEBHOOK_SECRET = process.env.ILETINIZ_WEBHOOK_SECRET;
// ÖNEMLİ: raw body gerekiyor — express.json() ÖNCE çağrılmamalı
app.post(
"/webhooks/sms-status",
express.raw({ type: "application/json" }),
(req, res) => {
const signature = req.headers["x-iletiniz-signature"];
if (!signature) {
return res.status(401).json({ error: "İmza eksik" });
}
// HMAC-SHA256 hesapla
const expectedSig = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(req.body) // Buffer (raw body)
.digest("hex");
// Zamanlama saldırılarına karşı sabit zamanlı karşılaştırma
const sigBuffer = Buffer.from(signature, "hex");
const expectedBuffer = Buffer.from(expectedSig, "hex");
if (
sigBuffer.length !== expectedBuffer.length ||
!crypto.timingSafeEqual(sigBuffer, expectedBuffer)
) {
return res.status(401).json({ error: "Geçersiz imza" });
}
// İmza doğrulandı — işlemi hızla 200 döndür, ağır işi async yap
res.status(200).json({ received: true });
const event = JSON.parse(req.body.toString("utf8"));
// Asenkron işle
setImmediate(() => handleDeliveryEvent(event));
}
);
async function handleDeliveryEvent(event) {
const { message_id, status, delivered_at } = event;
console.log(`[${message_id}] Durum: ${status} — ${delivered_at ?? "-"}`);
// Veritabanını güncelle, retry tetikle, metrik kaydet...
}
NedentimingSafeEqual? Standart string karşılaştırması (===), karakterleri sırayla karşılaştırdığından ilk farklı karakterde durur. Bu timing farkı, bir saldırganın imzayı kaba kuvvetle tahmin etmesine olanak tanıyabilir.timingSafeEqualher zaman aynı sürede çalışır.
Hata Yönetimi ve Sık Karşılaşılan Hatalar
Aşağıdaki tablo yaygın HTTP durum kodlarını ve önerilen aksiyonları özetler:
| HTTP Kodu | Anlam | Önerilen Aksiyon |
|---|---|---|
400 Bad Request |
Geçersiz numara formatı, eksik alan veya şablona uyumsuz değişken | İsteği düzeltin; retry anlamsız |
401 Unauthorized |
API anahtarı yanlış veya eksik | Anahtarı kontrol edin; ortam değişkenini doğrulayın |
403 Forbidden |
Hesap askıya alınmış ya da bu işlem için yetki yok | Destek ekibiyle iletişime geçin |
409 Conflict |
Aynı Idempotency-Key ile farklı parametreler gönderildi | Yeni bir Idempotency-Key üretin |
422 Unprocessable Entity |
Hesap bakiyesi yetersiz veya içerik politikası ihlali | Bakiye yükleyin / içeriği gözden geçirin |
429 Too Many Requests |
Rate limit aşıldı | Exponential backoff + Retry-After başlığını dikkate alın |
500 / 502 / 503 |
Sunucu hatası veya geçici servis kesintisi | Retry with backoff; defalarca tekrarlanırsa durum sayfasını kontrol edin |
SMS düzeyinde (HTTP 200 döndükten sonra) teslim başarısız olabilir: telefon kapalı, numara geçersiz, operatör reddi. Bu bilgiler yalnızca webhook üzerinden gelir; delivery status'u takip etmeden başarı sayımı yapamazsınız.
Test Ortamı ile Production Ayrımı
Geliştirme sürecinin tamamında iltz_test_... anahtarı kullanın. Test anahtarı:
- Gerçek SMS göndermez, dolayısıyla maliyet oluşturmaz.
- API'nin başarı ve hata yanıt yapılarını simüle eder.
- Webhook olaylarını test endpoint'inize tetikleyebilir (İletiniz panelinden manuel tetikleme desteği).
CI/CD pipeline'ınızda ILETINIZ_API_KEY değişkenini environment'a göre değiştirin: test branch'lerinde test anahtarı, production deploy'larında live anahtar. Bunu NODE_ENV veya benzeri bir değişkenle kod içinde kontrol etmekten kaçının; config yönetimi uygulama mantığından ayrı tutulmalıdır.
Güvenlik En İyi Uygulamaları
- API anahtarlarını rotate edin: 90 günde bir veya ekip üyesi ayrılığında anahtarı yenileyin.
- En az yetki prensibi: Mümkünse anahtara yalnızca gerekli izinleri (örn. "gönderim" yetkisi) verin.
- Webhook endpoint'ini IP kısıtlamayla koruyun: Sağlayıcının yayımladığı IP aralıklarından gelen isteklere izin verin.
- Replay saldırısı önleme: Webhook başlığındaki timestamp'i kontrol edin; 5 dakikadan eski istekleri reddedin.
- HTTPS zorunlu: API çağrıları ve webhook endpoint'leri hiçbir koşulda düz HTTP üzerinden çalıştırılmamalı.
- Loglarda PII saklamayın: Telefon numaraları ve mesaj içerikleri log sistemlerinde maskelenmeli veya tokenize edilmeli.
- OTP için kısa TTL: Tek kullanımlık kodları 5-10 dakika geçerli tutun; tüketilince veritabanından silin.
- Rate limiting uygulama tarafında: Aynı numaraya belirli bir süre içinde kaç OTP gönderilebileceğini sınırlandırın — kötüye kullanımı önler.
SMS API'nizi bugün başlatın: İletiniz ile ücretsiz hesap oluşturun. İlk 1.000 SMS ücretsiz, kredi kartı gerekmez. Kendi SMS sağlayıcı hesabınızı (NetGSM, İleti Merkezi, Verimor) bağlayın; dakikalar içinde API anahtarınızı alın ve gönderime başlayın.
Mesaj Şablonları ve Toplu Gönderim
Tekrar eden mesaj tipleri için (kargo bildirimi, OTP, randevu hatırlatma) İletiniz'in {{değişken}} destekli şablon sistemi kullanılabilir. Şablon bir kez tanımlanır; API çağrısında yalnızca değişken değerleri iletilir. Bu yaklaşımın avantajları:
- Mesaj içeriği kod tabanından ayrıştırılır; pazarlama ekibi geliştirici müdahalesi olmadan metni güncelleyebilir.
- Operatör ön onayı gerektiren içeriklerde şablon seviyesinde onay alınır.
- İçerik politikası ihlali riski azalır.
Yüzlerce veya binlerce alıcıya gönderim için API'nin toplu endpoint'i veya CSV/Excel yükleme özelliği kullanılabilir. Toplu gönderimlerde idempotency daha da kritik hale gelir: her alıcı için ayrı bir idempotency anahtarı üretin ve gönderim durumunu kayıt bazında takip edin.
Sık Sorulan Sorular
1. SMS API ile OTP gönderirken güvenliği nasıl sağlarım?
OTP'leri sunucu tarafında üretin, asla istemcide oluşturmayın. Kodları veritabanında hash'leyerek saklayın (bcrypt veya Argon2 ile). TTL'yi 5-10 dakika ile sınırlayın, tüketilince silerek tekrar kullanımı engelleyin. Aynı numaraya kısa sürede çok fazla OTP gönderimini rate limit ile kısıtlayın.
2. Idempotency-Key olmadan ne olur?
Ağ kesintisi veya timeout durumunda aynı isteği tekrar gönderirseniz kullanıcı birden fazla SMS alabilir. OTP senaryosunda bu, kod karışıklığına; bildirim senaryosunda ise kötü müşteri deneyimine yol açar. Her kritik gönderim için UUID v4 bazlı Idempotency-Key kullanmak iyi mühendislik pratiğidir.
3. Webhook imzasını neden timingSafeEqual ile doğrulamalıyım?
Standart string eşitlik kontrolü (===), ilk farklı byte'ta işlemi durdurduğundan timing farkı ortaya çıkar. Saldırgan bu farkı ölçerek doğru imzayı byte-byte tahmin edebilir (timing saldırısı). crypto.timingSafeEqual, tüm byte'ları her zaman eşit sürede karşılaştırarak bu saldırıyı önler.
4. Rate limit'e takılırsam ne yapmalıyım?
API 429 döndürdüğünde Retry-After başlığını okuyun ve belirtilen süre kadar bekleyin. Başlık yoksa exponential backoff + jitter uygulayın (örn. 1s, 2s, 4s, 8s aralarla). Yüksek hacimli gönderimler için API çağrılarını bir mesaj kuyruğu (Redis, SQS) üzerinden throttle edin.
5. Test anahtarı ile live anahtar arasındaki fark nedir?
Test anahtarı (iltz_test_...) gerçek SMS göndermez ve ücretlendirilmez; API yanıt yapısını simüle eder. Live anahtar (iltz_live_...) gerçek gönderim yapar. Geliştirme ve CI ortamında her zaman test anahtarı kullanın; live anahtarı yalnızca production ortamında etkinleştirin.
6. "Kendi sağlayıcını getir" modeli nasıl çalışır?
İletiniz, kendi SMS operatör altyapısına sahip değildir; bunun yerine NetGSM, İleti Merkezi veya Verimor gibi mevcut hesabınızı bağlamanızı sağlar. İletiniz, API, arayüz, şablonlar, raporlama ve webhook katmanını sunar; SMS maliyetleri sağlayıcınızla olan anlaşmanıza göre belirlenir.
7. Webhook'um aynı olayı birden fazla alırsa ne yapmalıyım?
İletiniz at-least-once (en az bir kez) teslim garantisi verdiğinden aynı webhook olayını birden fazla alabilirsiniz. Handler'ınızı idempotent yazın: olayın event_id alanını veritabanında saklayın ve tekrar işlenmesini önleyin. Önce 200 döndürüp ağır işlemi asenkron yapın; böylece provider'ın retry mekanizması tetiklenmez.

