Görevler ve webhook'lar
Medya üretimi (video, görsel, müzik) asenkron çalışır: bir görev (job) oluşturur ve hazır olduğunda sonucu alırsınız. Görevin tamamlandığını öğrenmenin iki yolu vardır: sorgulama (polling) ve webhook (push). Aşağıda görev yapısı ve sonucu güvenilir biçimde alma yöntemleri açıklanmaktadır.
Görev yaşam döngüsü
POST /media/generate → status: processing ──► done (result_url hazır)
│ └─► failed (error, tutar iade edildi)
└── (isteğe bağlı) callback_url → done/failed durumunda push webhook- Oluşturma. Model ve parametrelerle
POST /media/generateisteği gönderilir; yanıt olarakidve sorgulama bağlantısı döner:
{ "id": "beefb531-…", "model": "image/nano-banana-2", "status": "processing",
"poll": "https://nordrouter.com/media/job/beefb531-…" }Bekleme. Görev
processing→done(veyafailed) durumlarından geçer. Görseller saniyeler içinde, video ve müzik ise onlarca saniye ile birkaç dakika arasında tamamlanır.Sonuç. Tamamlanan görevin
result_urlalanı doludur (nordrouter.comüzerindeki bağlantı); dosya sınırlı bir süre saklanır (GET /media/modelsyanıtındakiretention_days) — kalıcı olarak saklamak için indirin.
GET /media/job/:id yanıtının (ve webhook gövdesinin — aşağıya bakın) biçimi aynıdır:
{ "id": "beefb531-…", "status": "done",
"result_url": "https://nordrouter.com/media/file/beefb531-…",
"cost_usd": 0.052, "error": null }status—processing|done|failed.result_url— yalnızcadonedurumunda doludur, aksi haldenull.result_url_2— müzik modellerinde (Suno) ikinci parça, aksi haldenull.results— görevin tüm dosyaları:[{ "index": 1, "name": "Результат 1", "ext": "png", "url": "…" }](3D için model ve önizleme, parça ayırma işleminde «Vokal», «Enstrümantal»… kanalları).cost_usd— gerçekleşen maliyet (işlem sonucuna göre tahsil edilir; başarısız üretimler ücretlendirilmez — ayrılan tutar iade edilir).error—faileddurumunda hata metni, aksi haldenull.
Yöntem 1. Sorgulama (polling)
En basit yol, status değeri done/failed olana kadar düzenli aralıklarla GET /media/job/:id isteği göndermektir:
curl https://nordrouter.com/media/job/beefb531-…2–3 saniyede bir sorgulayın. Sorgulama her zaman çalışır ve herkese açık bir adres gerektirmez — betikler, notebook'lar ve CI için uygundur.
2. Yöntem: Webhook’lar (push)
Sürekli sorgulama yapmamak için POST /media/generate isteğinde callback_url gönderin. Görev tamamlandığında adresinize POST isteği göndeririz:
curl https://nordrouter.com/media/generate \
-H "Authorization: Bearer sk-nr-YOUR-KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "image/nano-banana-2",
"input": { "prompt": "…" },
"callback_url": "https://your-server.com/webhooks/nordrouter",
"callback_secret": "sizin-belirleyeceğiniz-herhangi-bir-dize"
}'callback_url kabul edilirse, oluşturma yanıtında "webhook": "registered" yer alır. callback_url, görev oluşturulurken hemen doğrulanır: uygun değilse (https değilse veya özel/yerel bir adresse) istek, nedeni belirten 400 hatası döndürür ({ "error": "callback_url reddedildi: …", "code": "invalid_callback" }); görev oluşturulmaz.
Webhook gövdesi
Görev done/failed durumuna ulaştığında, callback_url adresinize GET /media/job/:id ile aynı gövde ve ek olarak event alanını içeren bir POST gönderilir:
{ "event": "media.completed", "id": "beefb531-…", "status": "done",
"result_url": "https://nordrouter.com/media/file/beefb531-…",
"cost_usd": 0.052, "error": null }event:media.completed(başarılı) veyamedia.failed(hata).
İmza doğrulama
callback_secret belirlediyseniz, X-NR-Signature başlığı sha256=<hmac> içerir. Bu, anahtar olarak sırrınız kullanılarak ham istek gövdesi üzerinden hesaplanan HMAC-SHA256 değeridir. İsteğin sahte olmadığını ve bizden geldiğini doğrulamak için imzayı kontrol edin:
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header) # sabit zamanlı karşılaştırmaimport crypto from "node:crypto";
function verify(rawBody, header, secret) {
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}JSON ayrıştırmasından önce, HMAC’i geldiği hâliyle tam olarak aynı baytlar üzerinden hesaplayın. Aksi takdirde imza eşleşmez.
10 satırda hazır webhook alıcısı
Webhook’u alan, imzayı doğrulayan ve 2xx yanıtı döndüren çalışan bir sunucu. Sır, callback_secret için gönderdiğiniz değerle aynı olmalıdır.
Node.js / Express:
import express from "express";
import crypto from "node:crypto";
const SECRET = "sizin-belirleyeceğiniz-herhangi-bir-dize"; // = callback_secret değeriniz
const app = express();
app.post("/webhooks/nordrouter",
express.raw({ type: "application/json" }), // ham gövde — imza için zorunlu
(req, res) => {
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const got = req.headers["x-nr-signature"] || "";
if (expected.length !== got.length || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got)))
return res.sendStatus(401); // imza eşleşmedi — istek bizden değil
const job = JSON.parse(req.body);
if (job.event === "media.completed") console.log("tamamlandı:", job.result_url, "$" + job.cost_usd);
res.sendStatus(200); // 2xx yanıtı verin, aksi hâlde tekrar gönderilir
});
app.listen(3000);Python / FastAPI:
import hmac, hashlib
from fastapi import FastAPI, Request, Response
SECRET = b"sizin-belirleyeceğiniz-herhangi-bir-dize" # = callback_secret değeriniz
app = FastAPI()
@app.post("/webhooks/nordrouter")
async def hook(request: Request):
raw = await request.body() # ham baytlar — imza için zorunlu
expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-NR-Signature", "")):
return Response(status_code=401) # imza eşleşmedi — istek bizden değil
job = await request.json()
if job["event"] == "media.completed":
print("tamamlandı:", job["result_url"], "$", job["cost_usd"])
return Response(status_code=200) # 2xx yanıtı verin, aksi hâlde tekrar gönderilirYerel ortamda test
callback_url herkese açık bir https adresi olmalıdır. Dizüstü bilgisayarda geliştirme yapmak için bir tünel açın; örneğin ngrok http 3000 komutunu çalıştırın ve oluşturulan https adresini callback_url olarak gönderin.
Teslimat ve güvenilirlik
- En fazla 4 deneme; bekleme süresi giderek artar (≈3 → 6 → 9 sn), her denemenin zaman aşımı 15 sn’dir. Aksi hâlde tekrar gönderileceğinden
2xxyanıtı verin. - Yönlendirmeler izlenmez. İsteği doğrudan
callback_urladresinde yanıtlayın. - Teslimat best-effort esasına dayanır: 4 denemenin tamamı başarısız olursa (sunucunuz çalışmıyorsa) webhook daha sonra yeniden gönderilmez. Bu nedenle:
Webhook’lar sorgulamanın yerini tutmaz
Webhook, garantili teslimat değil, daha hızlı bildirim sağlar. Yedek olarak GET /media/job/:id kullanmaya devam edin: push bildirimi makul bir süre içinde gelmezse durumu kendiniz sorgulayın. İdempotensi sizin sorumluluğunuzdadır: aynı görev hem webhook ile gelebilir hem de sorgulama sırasında görülebilir; id değerini esas alın.
callback_url gereksinimleri
- Yalnızca
https://adresleri kabul edilir. - Yalnızca herkese açık adresler kabul edilir. Yerel ve özel adresler (
localhost,*.local,10.x,172.16–31.x,192.168.x,127.x,169.254.x,100.64–127.x, IPv6 loopback/ULA/link-local) reddedilir. Bu, SSRF saldırılarına karşı bir korumadır. Adres, her gönderimden önce de yeniden doğrulanır (DNS rebinding koruması).
Hangisini seçmeli?
| Sorgulama | Webhook | |
|---|---|---|
Herkese açık https adresi | gerekmez | gerekir |
| Bildirimin ulaşma gecikmesi | sorgulama aralığına kadar | neredeyse anında |
| Garanti | kontrol sizde | best-effort + yedek olarak sorgulama |
| Uygun olduğu yerler | betikler, CI, dizüstü bilgisayar | sunucular, üretim arka uçları |
Öneri: Arka uçta webhook’u yedek olarak sorgulamayla birlikte kullanın; betiklerde ve dizüstü bilgisayarda yalnızca sorgulama yapın.