# HTTP ile teslim

Etkileşimleri gateway yerine kendi https adresine aldırabilirsin: N'oluyo her komutu, basmayı ve form gönderimini imzalı bir `POST` isteğiyle adresine yollar, sen de aynı isteğin yanıtında cevap verirsin.

## Hangi modu seçmeliyim

| İhtiyacın | Gateway | HTTP |
|---|---|---|
| Bot sürekli çalışan bir program | Uygun | Olur, ama gerek yok |
| Sunucusuz bir fonksiyon, yalnız istek gelince çalışan bir sunucu | Olmaz: gateway açık bir bağlantı ister | Uygun |
| Mesaj, üye, tepki gibi olayları dinlemek | Uygun | Olmaz: HTTP'ye yalnızca etkileşimler gelir |
| Yalnızca komut, buton, menü ve form | Uygun | Uygun |

Adres kaydettiğin an etkileşimler **yalnızca** adresine gelir, gateway'e `INTERACTION_CREATE` gelmez. Olayları da dinlemek istiyorsan gateway bağlantın açık kalır; ikisi birlikte çalışır. Adresi kaldırınca etkileşimler yeniden gateway'e döner.

## En küçük çalışan örnek

Node.js 22 veya üstü yeter, ek paket gerekmez. Program `3000` portunda düz HTTP dinler; önüne https sağlayan bir ters vekil (Caddy, nginx gibi) koyarsın, çünkü adres https olmalıdır. `/selam` komutuna yanıt verir:

```js title=http-bot.mjs
import { createServer } from "node:http";
import { createPublicKey, verify } from "node:crypto";

// Portaldaki "Herkese açık anahtar": 64 onaltılık karakter. Gizli değildir, ama koda gömme.
const PUBLIC_KEY = process.env.NOLUYO_PUBLIC_KEY;
const PORT = Number(process.env.PORT ?? 3000);

// Ham 32 baytlık Ed25519 anahtarını Node'un okuduğu SPKI biçimine saran sabit önek.
const SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
const key = createPublicKey({ key: Buffer.concat([SPKI_PREFIX, Buffer.from(PUBLIC_KEY, "hex")]), format: "der", type: "spki" });

const MAX_AGE = 5 * 60; // saniye
const seen = new Map(); // etkileşim kimliği -> geldiği an

function signatureOk(req, raw) {
  const sig = req.headers["x-signature-ed25519"];
  const ts = req.headers["x-signature-timestamp"];
  if (typeof sig !== "string" || !/^[0-9a-f]{128}$/.test(sig)) return false;
  if (typeof ts !== "string" || !/^\d+$/.test(ts)) return false;
  // Eski bir isteğin yeniden yollanması reddedilir.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > MAX_AGE) return false;
  return verify(null, Buffer.concat([Buffer.from(ts), raw]), key, Buffer.from(sig, "hex"));
}

function answer(i) {
  if (i.type === 1) return { type: 1 }; // PING
  if (i.type === 2 && i.data.name === "selam") {
    const user = i.member?.user ?? i.user;
    return { type: 4, data: { content: `Selam ${user.display_name ?? user.username}!` } };
  }
  return { type: 4, data: { content: "Bunu tanımıyorum.", flags: 64 } };
}

createServer((req, res) => {
  if (req.method !== "POST") return void res.writeHead(405).end();
  const chunks = [];
  let size = 0;
  req.on("data", (c) => {
    size += c.length;
    if (size > 1 << 20) req.destroy();
    else chunks.push(c);
  });
  req.on("end", () => {
    const raw = Buffer.concat(chunks);
    // Önce imza, ham gövdeyle. JSON'a ancak imza tutunca çevrilir.
    if (!signatureOk(req, raw)) return void res.writeHead(401).end("invalid request signature");
    let i;
    try {
      i = JSON.parse(raw);
    } catch {
      return void res.writeHead(400).end();
    }
    // Aynı istek ikinci kez gelirse iş ikinci kez yapılmaz.
    const now = Date.now();
    for (const [id, at] of seen) if (now - at > 2 * MAX_AGE * 1000) seen.delete(id);
    if (seen.has(i.id)) return void res.writeHead(204).end();
    seen.set(i.id, now);
    res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(answer(i)));
  });
}).listen(PORT, () => console.log(`Dinleniyor: ${PORT}`));
```

Çalıştırmak için:

```bash
NOLUYO_PUBLIC_KEY='portaldaki_acik_anahtar' node http-bot.mjs
```

`/selam` komutunu önce [Komutlar](https://noluyo.net/gelistirici/komutlar.md) sayfasındaki gibi kaydet. Sonra adresini portala yaz (aşağıda). Bu programın bot token'ına ihtiyacı yoktur; takip mesajı ya da `@original` düzenlemesi yapacaksan token'ı `process.env.BOT_TOKEN`'dan okursun.

## Adres kuralları

Adres kaydedilirken ve her bağlantıda şunlar denetlenir. Uymayan adres `endpoint_not_allowed` alır ve ona hiç istek gitmez.

| Kural | Neden |
|---|---|
| Yalnızca `https://` | İstek kişilerin verisini taşır; düz HTTP'de yolda okunabilir. |
| Port yok ya da `443` | Başka portlarda çoğunlukla iç servisler durur; N'oluyo bir port tarayıcısına dönmesin. |
| Kullanıcı adı ve şifre yok (`https://ad:sifre@...`) | Adresin içindeki şifre loglara düşer, ayrıca asıl host'u gizlemenin bilinen bir yoludur. |
| `#` yok | `#` sonrası sunucuya hiç gitmez; adresin ne olduğunu karıştırır. |
| Host bir ad olmalı, IP olmaz | IP adresi, ada bakan denetimleri atlatır. `https://203.0.113.5/` reddedilir. |
| Host yalnızca küçük-büyük İngilizce harf, rakam, `-` ve `.`, en az iki parça (`bot.ornek.com`) | Başka alfabelerden harfler bir adı başka bir ad gibi gösterebilir. `localhost` tek parçadır. |
| N'oluyo'nun kendi adresleri olmaz (`noluyo.net` ve alt alanları) | N'oluyo kendini çağırmasın. |
| En çok 2048 karakter | Daha uzunu bir adres değil, veri taşıma girişimidir. |
| Ad, her bağlantıda genel bir IP'ye çözülmeli | `10.x`, `192.168.x`, `127.0.0.1`, `::1` gibi yerel ve özel ağ adresleri reddedilir. Denetim DNS'ten sonra her bağlantıda yapılır, kayıttan sonra DNS'i değiştirmek işe yaramaz. |
| Yönlendirme izlenmez | `301`, `302` gibi bir yanıt başarısız sayılır. Adresin son adres olsun. |

Host'u küçük harfe çevirip sonundaki `.` ve `:443`'ü atarak kaydederiz. Sorgu dizesi (`?anahtar=...`) serbesttir ve aynen saklanır: N'oluyo onu loglara yazmaz, adresi yalnızca sen görürsün. İstersen adresine kendi gizli değerini koyup isteğin senden geldiğini bir kez daha denetleyebilirsin; asıl kanıt yine imzadır.

## Portalda adım adım

1. Programını çalıştır ve adresin dışarıdan https ile açıldığını denetle.
2. [Geliştirici portalında](https://noluyo.net/gelistirici/uygulamalar) uygulamanı aç, **Etkileşimler** bölümüne in.
3. **Herkese açık anahtar**'ı **Kopyala** ile kopyala ve programına `NOLUYO_PUBLIC_KEY` olarak ver. Anahtar her uygulamaya özeldir ve hiç değişmez.
4. **Etkileşim adresi** alanına adresini yaz, **Kaydet ve test et**'e bas.
5. N'oluyo adresine iki `PING` yollar (aşağıda). İkisi de doğru yanıtlanırsa adres kaydedilir ve **Kaydedildi** görürsün; o andan itibaren etkileşimler buraya gelir. Test geçmezse hiçbir şey kaydedilmez ve ne yapman gerektiği yazılır: örneğin imza denetimi yoksa "Geçersiz imzaya 401 dönmüyor. İmza denetimini ekle."
6. Gateway'e dönmek için **Adresi kaldır**'a bas.

Aynı işi otomasyonla yapmak için [Uygulamanı güncelle](https://noluyo.net/gelistirici/api-applications.md#updateApplication) rotasına `interactions_endpoint_url` gönder; `null` adresi kaldırır.

## İmzayı doğrula

Her isteğin iki başlığı vardır:

| Başlık | İçeriği |
|---|---|
| `X-Signature-Ed25519` | İmza, 128 onaltılık karakter |
| `X-Signature-Timestamp` | İsteğin yollandığı an, Unix saniyesi |

İmza, Ed25519 ile, `X-Signature-Timestamp`'in metni ve **ham gövdenin** baytları art arda eklenerek atılır. Doğrulamak için:

1. Gövdeyi bayt olarak oku. **Ham gövdeyi JSON'a çevirmeden doğrula**: çevirip yeniden yazarsan boşluklar ve alan sırası değişir, imza tutmaz.
2. Zaman damgası şimdiden 5 dakikadan eskiyse ya da ilerideyse reddet. Böylece yakalanmış eski bir istek yeniden yollanamaz.
3. `timestamp + gövde` üzerinde imzayı uygulamanın açık anahtarıyla doğrula.
4. Herhangi biri tutmazsa `401` dön ve hiçbir şey yapma.

> **Uyarı:** Geçersiz imzaya `401` dön. Adres kaydedilirken N'oluyo bilerek yanlış imzalı bir istek yollar; `401` almazsa adresi kaydetmez. İmzayı denetlemeyen bir adres, adresini bilen herkesin botuna sahte komut yollamasına açıktır.

Python'da doğrulama, standart kütüphanede Ed25519 olmadığı için ek bir paket ister: `pip install cryptography`.

```python
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
import os, time

KEY = Ed25519PublicKey.from_public_bytes(bytes.fromhex(os.environ["NOLUYO_PUBLIC_KEY"]))

def signature_ok(headers, raw: bytes) -> bool:
    sig, ts = headers.get("X-Signature-Ed25519", ""), headers.get("X-Signature-Timestamp", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > 300:
        return False
    try:
        KEY.verify(bytes.fromhex(sig), ts.encode() + raw)
        return True
    except (InvalidSignature, ValueError):
        return False
```

## PING ve iki istekli test

Adresi kaydederken N'oluyo şu gövdeyi iki kez yollar:

```json
{ "id": "112794120000000000", "application_id": "112793931040686080", "type": 1, "token": "", "version": 1 }
```

| İstek | Beklenen yanıt |
|---|---|
| Doğru imzalı `PING` | `200` ve gövdede `{"type":1}` |
| Yanlış imzalı `PING` | `401` |

Neden iki istek: ilki adresin gerçekten bu botun programı olduğunu gösterir, çünkü yalnızca senin programın `PING`'e `{"type":1}` döner; böylece kimse N'oluyo'yu başkasının adresine yönlendiremez. İkincisi programın imzayı gerçekten denetlediğini gösterir. Test kişiyi bekletir: iki istek birlikte en çok 6 saniye kadar sürer ve kişi başına dakikada en çok 5 test yapılır.

Bot çalışırken de arada bir `PING` gelebilir; her zaman `{"type":1}` dön.

## Yanıt ver

İstek, gateway'deki `INTERACTION_CREATE` olayının `d` alanıyla aynı nesnedir; alanlar [Etkileşimler](https://noluyo.net/gelistirici/etkilesimler.md) ve [Komutlar](https://noluyo.net/gelistirici/komutlar.md) sayfalarında. Yanıtın üç biçimi vardır:

| Durum | Gövde | Anlamı |
|---|---|---|
| `200` | JSON: `{ "type": ..., "data": ... }` | Yanıtın kendisi, [callback rotasına](https://noluyo.net/gelistirici/etkilesimler.md#yanit-rotasi) gönderdiğinle aynı biçim: `4`, `5`, `6`, `7`, otomatik tamamlamada `8`, formda `9`. |
| `202` ya da `204` | Boş | "Yanıtı `callback` rotasıyla vereceğim." Bu bir yanıt sayılmaz; 3 saniye kuralı sürer, `callback`'i yine zamanında göndermelisin. |
| Başka her şey | | Başarısız: kişi botun yanıt vermediğini görür. |

Kişinin isteği sen yanıt verene kadar açık bekler, en çok 3 saniye. `200` gövdesi tek bir JSON nesnesidir; tanımadığımız alanlar yok sayılır. Gövde, `callback` rotasına göndereceğin yanıtla aynı denetimlerden ve sınırlardan geçer. Tek fark: reddedilirse hata kodunu sen görmezsin, kişi botun yanıt vermediğini görür ve kod [teslim günlüğüne](https://noluyo.net/gelistirici/http-teslim.md#devre-kesici-ve-teslim-gunlugu) yazılır.

`200` yanıtı dosya taşıyamaz, çünkü JSON'dur. Görselli yanıt için `5` dön, sonra görseli `@original` düzenlemesiyle ya da takip mesajıyla yolla. Yanıt en çok 64 KiB olabilir.

`200` ile mesaj yazan bir yanıt da gateway'den gelen botların sınırlarına sayılır: aynı yazma tavanı, aynı yayılma bütçesi, aynı `interaction` sınıfı. Yoğunlukta kişi `interaction_busy` görür; bu senin botunun suçu olmayabilir, tüm botların ortak tavanı dolu olabilir. HTTP, sınırlardan kaçış yolu değildir. Ayrıntı [Hız sınırları](https://noluyo.net/gelistirici/rate-limit.md#tum-botlarin-ortak-tavani) sayfasında.

### Her tür için gövde

| Ne istiyorsun | `200` gövdesi |
|---|---|
| Komuta ya da basmaya yeni mesajla yanıt | `{"type":4,"data":{"content":"Selam!"}}` |
| Yalnızca kişi görsün | `{"type":4,"data":{"content":"Yalnızca sen görüyorsun.","flags":64}}` |
| Uzun iş: önce "düşünüyor" | `{"type":5}` |
| Butona sessiz onay | `{"type":6}` |
| Basılan mesajı güncelle | `{"type":7,"data":{"content":"Sayaç: 2"}}` |
| Otomatik tamamlama önerileri | `{"type":8,"data":{"choices":[{"name":"Ankara","value":"ankara"}]}}` |
| Form aç | `{"type":9,"data":{"custom_id":"sikayet","title":"Şikayet","components":[{"type":1,"components":[{"type":4,"custom_id":"konu","style":1,"label":"Konu"}]}]}}` |

Hangi etkileşimin hangi türü aldığı [Etkileşimler](https://noluyo.net/gelistirici/etkilesimler.md#hangi-yaniti-secmeliyim) sayfasında. Gönderilen form da adresine yeni bir etkileşim (`type: 5`) olarak gelir.

### Uzun iş: önce 5, sonra @original

3 saniyeye sığmayacak işte hemen `{"type":5}` dön, işi yanıttan sonra bitir ve mesajı bot token'ıyla doldur. Token'ın ömrü 15 dakikadır. Yukarıdaki `http-bot.mjs`'te `answer` işlevini bununla değiştir:

```js title=http-bot.mjs
const API = "https://noluyo.net/api/v1";

// Senin uzun işin; burada 5 saniye bekliyor.
async function makeReport() {
  await new Promise((done) => setTimeout(done, 5000));
  return "Rapor hazır.";
}

async function finish(i) {
  const content = await makeReport();
  const res = await fetch(`${API}/interactions/${i.id}/${i.token}/messages/@original`, {
    method: "PATCH",
    headers: { Authorization: `Bot ${process.env.BOT_TOKEN}`, "Content-Type": "application/json" },
    body: JSON.stringify({ content }),
  });
  if (!res.ok) console.error("Doldurulamadı:", res.status, await res.text());
}

function answer(i) {
  if (i.type === 1) return { type: 1 }; // PING
  if (i.type === 2 && i.data.name === "rapor") {
    finish(i).catch(console.error); // yanıt gittikten sonra sürer
    return { type: 5 };
  }
  if (i.type === 2 && i.data.name === "selam") {
    const user = i.member?.user ?? i.user;
    return { type: 4, data: { content: `Selam ${user.display_name ?? user.username}!` } };
  }
  return { type: 4, data: { content: "Bunu tanımıyorum.", flags: 64 } };
}
```

Çalıştırmak için token'ı da ver:

```bash
NOLUYO_PUBLIC_KEY='portaldaki_acik_anahtar' BOT_TOKEN='bot_tokenin' node http-bot.mjs
```

### 202 ile, callback rotasından

İstersen yanıtı gövdede değil, gateway'deki gibi `callback` rotasıyla ver: hemen `202` dön, sonra 3 saniye içinde rotayı çağır. Bu, yanıtı başka bir süreçte hazırlayan botlar içindir; tek süreçte gövdeyle yanıt vermek daha kısadır.

```js
// createServer içinde, imza tuttuktan sonra, answer() yerine:
res.writeHead(202).end();
fetch(`${API}/interactions/${i.id}/${i.token}/callback`, {
  method: "POST",
  headers: { Authorization: `Bot ${process.env.BOT_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ type: 4, data: { content: "Selam!" } }),
}).catch(console.error);
```

### Kişi ne görür

| Durum | Kişinin isteği | Kişi ne görür |
|---|---|---|
| `200` ve yanıt geçti | `200` | Botun yanıtı |
| `202`, `204` | `200` | `callback` gelene dek bekler; 3 saniyede gelmezse "Bot yanıt vermedi" |
| Yanıt gelmedi, reddedildi ya da devre kesici açık | `504 application_no_response` | "Bot zamanında yanıt vermedi." |
| Adresine aynı anda 16 teslim gidiyor ya da tüm botların tavanı dolu | `429 interaction_busy` | "Bot şu an yoğun. Biraz sonra yeniden dene." |

Kişi beklerken sayfayı kapatsa da yanıtın işlenir.

## Zaman, tekrar ve tekilleştirme

- **3 saniye.** Bağlanma, TLS ve yanıtın tamamı 3 saniyeyi geçerse istek başarısız sayılır. Uzun iş yapacaksan önce `5` dön, işi sonra bitir.
- **Tekrar deneme yok.** Kişi beklediği için her etkileşim adresine en çok bir kez yollanır. Başarısız olursa kişi hatayı hemen görür ve isterse yeniden dener; bu yeni bir etkileşimdir.
- **Kimliğe göre tekilleştir.** İmzalı bir isteği yakalayan biri onu 5 dakika içinde yeniden yollayabilir. Gördüğün `id`'leri en az 10 dakika tut ve aynısı gelirse işi ikinci kez yapma.
- **Aynı anda en çok 16 istek.** Bir uygulamanın adresine aynı anda en çok 16 teslim gider; dolarsa kişi hemen `interaction_busy` görür.

## Devre kesici ve teslim günlüğü

Adresin art arda 10 kez başarısız olursa N'oluyo onu 30 saniye aramaz (portal günlüğün üstünde "Adres yanıt vermediği için teslim 30 saniye durdu." yazar); bu sürede kişiler botun yanıt vermediğini hemen görür. Sonra 30 saniyede bir tek bir deneme yapar, biri başarılı olunca normale döner. Yalnızca adresinin kendi hataları sayılır: `timeout`, `dns`, `connect`, `tls`, `redirect`, `status`, `too_large`, `not_allowed` ve `bad_json`. Adresinin yanıt verdiği ama yanıtın uygulanırken reddedildiği teslimler (`busy`, `error` ve hata kodları, örneğin kişi DM'yi kapattığı için `dm_closed`) günlükte görünür, ama bu sayıma girmez ve sayımı sıfırlamaz: onlar adresinin değil, kişinin ya da tavanın sonucudur. Yeni bir adres kaydedince sayım ve günlük sıfırlanır.

Portalda **Etkileşimler** bölümündeki **Teslim günlüğü** (**Yenile** ile tazelenir), adresine yapılan son 50 teslimi en yenisi önce gösterir. Günlük bellekte tutulur, N'oluyo yeniden başlarsa boşalır. Kişi ve içerik yoktur:

| Sütun | Anlamı |
|---|---|
| Zaman | Teslimin anı |
| Tür | Etkileşimin türü: `2` komut, `3` bileşen, `4` otomatik tamamlama, `5` form |
| Durum | Adresinin HTTP durumu, hiç yanıt gelmediyse `0` |
| Süre | Milisaniye |
| Sonuç | Aşağıdaki tablo |

| Sonuç | Ne oldu | Ne yap |
|---|---|---|
| `ok` | Başarılı | |
| `timeout` | 3 saniyede yanıt gelmedi | İşi kısalt ya da önce `5` dön. |
| `dns` | Ad çözülemedi | DNS kaydını denetle. |
| `connect` | Bağlanılamadı | Sunucun ve 443 portu açık mı bak. |
| `tls` | TLS kurulamadı | Sertifikan geçerli ve adla eşleşiyor mu bak. |
| `redirect` | `3xx` döndü | Son adresi kaydet. |
| `status` | `200`, `202` ve `204` dışında bir durum | Programının loguna bak; geçersiz imzaya `401` doğrudur, geçerliye değil. |
| `too_large` | Yanıt 64 KiB'tan büyük | Yanıtı küçült. |
| `not_allowed` | Ad yerel ya da özel bir IP'ye çözüldü | Adın genel bir IP göstermeli. |
| `bad_json` | `200` geldi ama gövde tek bir JSON nesnesi değil | Gövdeyi `JSON.stringify` ile yaz, ardına bir şey ekleme. |
| `busy` | Yanıt geldi ama tüm botların ortak tavanı doluydu; kişi `interaction_busy` gördü | Bir şey yapma; sürerse [Hız sınırları](https://noluyo.net/gelistirici/rate-limit.md) sayfasına bak. |
| `error` | Bizim tarafımızda bir hata | Sürerse bize bildir. |
| Bir hata kodu | Yanıt geldi ama reddedildi; `callback` rotası da aynı kodu dönerdi: `interaction_response_invalid`, `validation_failed`, `dm_closed`, `application_unavailable` gibi | Kodun anlamı [Hatalar](https://noluyo.net/gelistirici/hatalar.md) sayfasında. |

Aynı bilgiyi [Teslim günlüğünü oku](https://noluyo.net/gelistirici/api-applications.md#getEndpointDeliveries) rotası da verir; `open: true` devre kesicinin açık olduğunu söyler.

## Yerelde nasıl denersin

`https://localhost:3000` kaydedilemez: N'oluyo'nun sunucuları senin bilgisayarına ulaşamaz, yerel adresler de zaten reddedilir. Denemek için şu yollardan birini seç:

- Bilgisayarındaki `3000` portunu genel bir https adrese açan bir tünel servisi kullan ve onun verdiği adresi kaydet. İşin bitince adresi kaldır.
- Programı ucuz bir sunucuya ya da sunucusuz bir fonksiyona koy.
- Kayıttan önce imza kodunu kendi testinle dene: yanlış imzaya `401`, doğru `PING`'e `{"type":1}` dönmeli.

## Limitler

| Anahtar | Değer | Saniye | Anlamı |
|---|---|---|---|
| `verify` | 5 | 60 | Kişi başına adres testi |
| `timeout` | 3 | | Bir teslimin tamamı |
| `response` | 65536 | | Yanıtın en büyük boyutu, bayt |
| `in_flight` | 16 | | Bir uygulamaya aynı anda giden teslim |
| `trip` | 10 | 30 | Bu kadar ardışık hatadan sonra adres bu kadar saniye aranmaz |
| `freshness` | 300 | | Kabul edeceğin en eski zaman damgası, saniye |
| `url` | 2048 | | Adresin en çok karakteri |

## Hatalar

Adres kaydedilirken dönebilecek hatalar. Yalnızca hatanın türü söylenir; adresinin döndüğü durum ya da metin söylenmez.

| Kod | Ne oldu | Ne yap |
|---|---|---|
| `endpoint_not_allowed` | Adres kurallara uymuyor ya da ad yerel bir IP'ye çözülüyor | Yukarıdaki tabloya bak. |
| `endpoint_unreachable` | Adrese ulaşılamadı ya da 3 saniyede yanıt gelmedi | Programın açık mı, https ve sertifika doğru mu bak. |
| `endpoint_bad_response` | Doğru imzalı `PING`'e `200 {"type":1}` dönmedi | `type: 1` gelince `{"type":1}` dön; yönlendirme yapma. |
| `endpoint_no_signature_check` | Yanlış imzalı isteğe `401` dönmedi | İmzayı her istekte doğrula, tutmazsa `401` dön. |
| `rate_limited` | Dakikada 5 testi aştın | `retry_after` kadar bekle. |

## Bu sayfayı yapay zekaya verirsen

> **İpucu:** Bu sayfayı bir yapay zeka asistanına verip kod yazdırıyorsan şu kuralları da ekle:
>
> - Token koda yazılmaz, `process.env.BOT_TOKEN`'dan okunur; açık anahtar `process.env.NOLUYO_PUBLIC_KEY`'den.
> - Etkileşime ilk yanıt 3 saniye içinde verilir.
> - Her istekte imza, JSON'a çevirmeden önce ham gövde üzerinde doğrulanır: `X-Signature-Timestamp` + gövde, Ed25519; tutmazsa `401`.
> - 5 dakikadan eski zaman damgası reddedilir; aynı `id` ikinci kez işlenmez.
> - `type: 1` (`PING`) gelince `200` ve `{"type":1}` dönülür.
> - Yanıt `200` ile JSON `{type, data}`; dosya yoksa doğrudan, varsa önce `5` sonra takip mesajı. `202` ya da `204` yanıt sayılmaz.
> - Uzun iş için önce `{"type":5}` dönülür, iş yanıttan sonra bitirilir ve `PATCH .../messages/@original` ile doldurulur.
> - Yönlendirme yapılmaz; adres https ve 443'tür.

## Sırada ne var

- [Etkileşimler](https://noluyo.net/gelistirici/etkilesimler.md): HTTP yanıt gövdesinde ya da callback rotasında dönen yanıt türleri
- [JavaScript kütüphanesi](https://noluyo.net/gelistirici/kutuphane-js.md) ve [Python kütüphanesi](https://noluyo.net/gelistirici/kutuphane-python.md): yerleşik `verifyInteraction` / `verify_interaction` yardımcıları
- [Güvenlik](https://noluyo.net/gelistirici/guvenlik.md): imza doğrulama ve gizli anahtar yönetimi
