Skip to content

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
  1. Pembuatan. POST /media/generate dengan model dan parameter - menampilkan id dan tautan untuk survei:
json
{ "id": "beefb531-…", "model": "image/nano-banana-2", "status": "processing",
  "poll": "https://nordrouter.com/media/job/beefb531-…" }
  1. Menunggu. Tugas melewati processing → done (atau failed). Gambar - detik, video dan musik - dari puluhan detik hingga beberapa menit.

  2. Hasil. Tugas yang telah diselesaikan telah result_url terisi (tautan ke nordrouter.com); file disimpan untuk waktu terbatas (retention_days dari GET /media/models) - unduh untuk menyimpan.

Format respons GET /media/job/:id (dan isi webhook - lihat di bawah) adalah sama:

json
{ "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 dengan done, jika tidak null.
  • result_url_2 - trek kedua untuk model musik (Suno), jika tidak null.
  • cost_usd - biaya sebenarnya (dihapuskan setelah kejadian; pembangkitan yang gagal tidak dikenakan biaya - cadangan dikembalikan).
  • error - teks kesalahan untuk failed, jika tidak null.

Metode 1. Polling ​

Cara termudah adalah dengan meminta GET /media/job/:id secara berkala hingga status menjadi done/failed:

bash
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:

bash
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:

json
{ "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) atau media.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:

python
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)  # сравнение за константное время
javascript
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:

javascript
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:

python
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 ​

SurveiWebhook
Publik https alamattidak diperlukandiperlukan
Penundaan penerimaanhingga interval pemungutan suarahampir seketika
JaminanAnda mengontrolupaya terbaik + survei sebagai cadangan
Jika nyaman, skrip, CI, server laptop, backend produksi

Rekomendasi: di backend - webhook + survei sebagai asuransi; di skrip/laptop - hanya survei.