Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.iletiniz.com/llms.txt

Use this file to discover all available pages before exploring further.

İletiniz webhook’unun gerçekten iletiniz tarafından gönderildiğini doğrulamak için her istek HMAC-SHA256 ile imzalanır. Endpoint’inizde bu imzayı mutlaka doğrulayın — aksi halde sunucunuza sahte event’ler gönderilebilir.

Gönderilen Başlıklar

POST /iletiniz/webhook HTTP/1.1
Host: app.firma.com
Content-Type: application/json
User-Agent: iletiniz-webhook/1.0
X-Iletiniz-Timestamp: 1747166040
X-Iletiniz-Nonce: 5e8a3c2b1f9d7e6a4b2c1d0e9f8a7b6c
X-Iletiniz-Signature: 9d7e6a4b2c1d0e9f8a7b6c5e8a3c2b1f9d7e6a4b2c1d0e9f8a7b6c5e8a3c2b1f

{"id":"...","event":"message.delivered",...}
BaşlıkAçıklama
X-Iletiniz-Timestampİsteğin imzalandığı anın Unix saniye değeri.
X-Iletiniz-NonceHer istek için yeniden üretilen 16-byte hex string. Replay korumasında kullanılır.
X-Iletiniz-Signaturehex(hmac_sha256(secret, "${timestamp}.${nonce}.${sha256_hex(body)}")).

İmza Şeması

İletiniz tarafında imza şöyle üretilir:
const bodyHash = sha256(rawBody);                 // hex
const stringToSign = `${timestamp}.${nonce}.${bodyHash}`;
const signature = hmacSha256(secret, stringToSign); // hex
Endpoint tarafında doğrulama tam tersini yapar.

Doğrulama Örnekleri

import crypto from "node:crypto";

const SECRET = process.env.ILETINIZ_WEBHOOK_SECRET;

app.post("/iletiniz/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const ts = req.get("X-Iletiniz-Timestamp");
  const nonce = req.get("X-Iletiniz-Nonce");
  const sig = req.get("X-Iletiniz-Signature");

  // 1. Timestamp tazeliği (±5 dakika)
  if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > 300) {
    return res.status(401).send("Stale timestamp");
  }

  // 2. İmza üret
  const bodyHash = crypto.createHash("sha256").update(req.body).digest("hex");
  const stringToSign = `${ts}.${nonce}.${bodyHash}`;
  const expected = crypto.createHmac("sha256", SECRET).update(stringToSign).digest("hex");

  // 3. Sabit-zaman karşılaştırma
  const ok = sig && crypto.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"));
  if (!ok) return res.status(401).send("Invalid signature");

  // 4. (Opsiyonel) Nonce'u Redis SET ile dedupe et
  // const fresh = await redis.set(`iltz:nonce:${nonce}`, "1", "EX", 600, "NX");
  // if (!fresh) return res.status(200).end();  // replay

  const event = JSON.parse(req.body.toString("utf8"));
  // ... event işleme
  res.status(200).end();
});

Önemli Notlar

JSON.parse + JSON.stringify yuvarlağına soktuğunuz an key sırası, boşluk ve sayı formatı değişebilir, imza tutmaz. Mutlaka byte-byte aynı body üzerinden sha256 alın. Express için express.raw(), FastAPI için await req.body().
== ile karşılaştırma timing attack’a açıktır. crypto.timingSafeEqual (Node), hmac.compare_digest (Python), hash_equals (PHP), hmac.Equal (Go) kullanın.
İmza geçerli olsa bile eski bir istek replay edilebilir. ±5 dakika (300 sn) tolerans dışı timestamp’leri reddedin.
Dashboard’da endpoint düzenleyerek yeni secret üretebilirsiniz. Yeni secret üretildiği anda eski geçersizleşir; rolling deploy planlayın (örn. yeni secret’ı önceden env’e yaz, sonra dashboard’dan değiştir, sonra eskiyi sil).