Tugas dan webhook
Pembuatan media (video, gambar, musik) bersifat asinkron: Anda membuat tugas (pekerjaan) dan mengambil hasilnya ketika sudah siap. Ada dua cara untuk mengetahui bahwa suatu tugas telah selesai: poll (polling) dan webhook (push). Di bawah ini adalah cara kerja tugas dan cara mendapatkan hasil yang andal.
Siklus hidup tugas
POST /media/generate → status: processing ──► done (result_url готов)
│ └─► failed (error, средства возвращены)
└── (опционально) callback_url → пуш-вебхук на done/failed- Pembuatan.
POST /media/generatedengan model dan parameter - menampilkaniddan tautan untuk survei:
{ "id": "beefb531-…", "model": "image/nano-banana-2", "status": "processing",
"poll": "https://nordrouter.com/media/job/beefb531-…" }Menunggu. Tugas melewati
processing→done(ataufailed). Gambar - detik, video dan musik - dari puluhan detik hingga beberapa menit.Hasil. Tugas yang telah diselesaikan telah
result_urlterisi (tautan kenordrouter.com); file disimpan untuk waktu terbatas (retention_daysdariGET /media/models) - unduh untuk menyimpan.
Format respons GET /media/job/:id (dan isi webhook - lihat di bawah) adalah sama:
{ "id": "beefb531-…", "status": "done",
"result_url": "https://nordrouter.com/media/file/beefb531-…",
"cost_usd": 0.052, "error": null }status—processing|done|failed.result_url- diisi hanya dengandone, jika tidaknull.result_url_2- trek kedua untuk model musik (Suno), jika tidaknull.cost_usd- biaya sebenarnya (dihapuskan setelah kejadian; pembangkitan yang gagal tidak dikenakan biaya - cadangan dikembalikan).error- teks kesalahan untukfailed, jika tidaknull.
Metode 1. Polling
Cara termudah adalah dengan meminta GET /media/job/:id secara berkala hingga status menjadi done/failed:
curl https://nordrouter.com/media/job/beefb531-…Jajak pendapat setiap 2-3 detik sekali. Survei selalu berfungsi dan tidak memerlukan alamat publik - cocok untuk skrip, laptop, CI.
Metode 2. Webhook (push)
Untuk menghindari polling, teruskan callback_url ke POST /media/generate, dan kami akan mengirimkan POST ke alamat Anda saat tugas selesai:
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": "любая-ваша-строка"
}'Jika callback_url diterima, respons pembuatan berisi "webhook": "registered". callback_url dicentang segera saat membuat tugas: jika tidak sesuai (bukan https, alamat pribadi/lokal) - permintaan akan mengembalikan 400 dengan alasan, tugas tidak dibuat.
Badan webhook
Ketika tugas mencapai done/failed, callback_url Anda menerima POST dengan isi yang sama dengan GET /media/job/:id, ditambah bidang event:
{ "event": "media.completed", "id": "beefb531-…", "status": "done",
"result_url": "https://nordrouter.com/media/file/beefb531-…",
"cost_usd": 0.052, "error": null }event-media.completed(berhasil) ataumedia.failed(kesalahan).
Verifikasi tanda tangan
Jika Anda menyetel callback_secret, header X-NR-Signature berisi sha256=<hmac> - HMAC-SHA256 dari isi permintaan mentah, dengan kunci = rahasia Anda. Periksa untuk memastikan permintaan tersebut dari kami dan bukan palsu:
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) # сравнение за константное времяimport 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));
}Baca HMAC dari bytes persis seperti yang diterima ke penguraian JSON - jika tidak, tanda tangan tidak akan cocok.
Penerima siap dalam 10 baris
Server berfungsi yang menerima webhook memeriksa tanda tangan dan merespons dengan 2xx. Rahasianya sama dengan yang Anda berikan ke callback_secret.
Node.js / Express:
import express from "express";
import crypto from "node:crypto";
const SECRET = "любая-ваша-строка"; // = ваш callback_secret
const app = express();
app.post("/webhooks/nordrouter",
express.raw({ type: "application/json" }), // сырое тело — обязательно для подписи
(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); // подпись не сошлась — не наш запрос
const job = JSON.parse(req.body);
if (job.event === "media.completed") console.log("готово:", job.result_url, "$" + job.cost_usd);
res.sendStatus(200); // ответьте 2xx, иначе будет повтор
});
app.listen(3000);Python / FastAPI:
import hmac, hashlib
from fastapi import FastAPI, Request, Response
SECRET = b"любая-ваша-строка" # = ваш callback_secret
app = FastAPI()
@app.post("/webhooks/nordrouter")
async def hook(request: Request):
raw = await request.body() # сырые байты — обязательно для подписи
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) # подпись не сошлась — не наш запрос
job = await request.json()
if job["event"] == "media.completed":
print("готово:", job["result_url"], "$", job["cost_usd"])
return Response(status_code=200) # ответьте 2xx, иначе будет повторPeriksa secara lokal
callback_url harus bersifat publik https. Untuk mengembangkan di laptop, naikkan terowongan - misalnya ngrok http 3000 - dan berikan alamat https yang dikeluarkan olehnya sebagai callback_url.
Pengiriman dan keandalan
- Hingga 4 percobaan dengan jeda yang bertambah (≈3 → 6 → 9 dtk), setiap batas waktu adalah 15 dtk. Balas
2xx, jika tidak maka akan terulang kembali. - Pengalihan tidak berfungsi - tanggapi langsung ke
callback_url. - Pengiriman - usaha terbaik: jika keempat upaya gagal (server Anda mati), webhook tidak dikirim ulang nanti. Oleh karena itu:
Webhook bukanlah pengganti survei
Webhook adalah percepatan, bukan jaminan. Simpan GET /media/job/:id sebagai rute cadangan: jika push tidak tiba dalam waktu yang wajar, lakukan polling sendiri statusnya. Idempotensi ada di pihak Anda: tugas yang sama dapat muncul sebagai webhook dan diperhatikan melalui survei - fokus pada id.
Persyaratan untuk callback_url
- Hanya
https://. - Hanya alamat publik. Alamat lokal dan pribadi (
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) reject adalah perlindungan SSRF. Alamat diperiksa ulang sebelum setiap pengiriman (perlindungan terhadap pengikatan ulang DNS).
Apa yang harus dipilih
| Survei | Webhook | |
|---|---|---|
Publik https alamat | tidak diperlukan | diperlukan |
| Penundaan penerimaan | hingga interval pemungutan suara | hampir seketika |
| Jaminan | Anda mengontrol | upaya terbaik + survei sebagai cadangan |
| Jika nyaman, skrip | , CI, server laptop | , backend produksi |
Rekomendasi: di backend - webhook + survei sebagai asuransi; di skrip/laptop - hanya survei.