Задачі і веб-хоки
Генерація медіа (від'єки, зображення, музика) асинхронна: ви створюєте задача (задача) і отримуєте результат, коли вона готова. Є два способи дізнатися, що завдання завершено: опитування (опитування) і веб-бук (поштовх). Нижче як організовано завдання і як отримати надійний результат.
Життєвий цикл задач
POST /media/generate → status: processing ──► done (result_url готов)
│ └─► failed (error, средства возвращены)
└── (опционально) callback_url → пуш-вебхук на done/failed- Створення.
POST /media/generateз моделем і параметрами повертаєidі посилання на опитування:
{ "id": "beefb531-…", "model": "image/nano-banana-2", "status": "processing",
"poll": "https://nordrouter.com/media/job/beefb531-…" }Чек. Задача проходить
processing→done(абоfailed).Результат. У виконаній задачі заповнений
result_url(посылок наnordrouter.com); файл зберігається обмежений час (retention_daysзGET /media/models) завантажити, щоб зберегти.
Формат відповіді GET /media/job/:id (і тіла вебхука см. нижче) однаковий:
{ "id": "beefb531-…", "status": "done",
"result_url": "https://nordrouter.com/media/file/beefb531-…",
"cost_usd": 0.052, "error": null }status—processing|done|failed.result_urlзаповнений тільки приdone, інакшеnull.result_url_2другий трек у музичних моделях (Suno), інакшеnull.cost_usdФактична вартість (записється по факту; невдалені генерації не тарифіруються резерв повертається).errorтекст помилки приfailed, інакшеnull.
Метод 1. Опитування (опитування)
Найпростіший спосіб - періодично запитувати GET /media/job/:id, поки status не стане done/failed:
curl https://nordrouter.com/media/job/beefb531-…Запитання працює завжди і не вимагає публічного адресу, підходить для скриптів, ноутбуків, CI.
Метод 2. Вебхуки (пуш)
Щоб не задати запит, передайте callback_url на POST /media/generate, і ми сами поправляємо POST до вашого адресу, коли завдання буде завершено:
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": "любая-ваша-строка"
}'Якщо callback_url прийнято, відповідь на створення містить "webhook": "registered". callback_url перевіряється разу при створенні завдання: якщо він не підходить (не https, приватний/міський адрес) запит повертає 400 з причиною, завдання не створюється.
Тело вебхука
Коли завдання досягає done/failed, то на ваш callback_url йде POST з таким же телом, що і GET /media/job/:id, плюс поле event:
{ "event": "media.completed", "id": "beefb531-…", "status": "done",
"result_url": "https://nordrouter.com/media/file/beefb531-…",
"cost_usd": 0.052, "error": null }eventmedia.completed(успіх) абоmedia.failed(пошибка).
Перевірка підпису
Якщо ви запустили callback_secret, заголовки X-NR-Signature містять sha256=<hmac> HMAC-SHA256 від чистого тіла запиту, з ключем = вашим секретом.
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));
}Читайте HMAC від байтів так само, як вони прийшли, до JSON-парсингу інакше підпис не підходить.
Готовий приймач за 10 строк
Працючий сервер, який приймає веб-хук, перевіряє підпис і відповідає. 2xxСекрет - той же, що і той, який ви передали в . 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, иначе будет повторПроверяйте локально
callback_url Він повинен бути публічним. httpsДля розробки на ноутбуку піднімайте тунель , наприклад . ngrok http 3000 і передайте їм виданий https-адрес як callback_url.
Доставка і надійність
- До 4 спроб з зростаючою паузою (≈3 → 6 → 9 c), тимчасовий час кожного 15 c. Ответьте
2xx, інакше це буде повторювання. - Перенаправлення не пройдуть, відповідайте безпосередньо по адресі
callback_url. - Доставка best-effort: якщо всі 4 спроби не вдалися (в ваш сервер лежав), веб-хук не переправляється пізніше.
Веб-хукі не замінюють опитування.
Вебхук - це прискорення, а не гарантія. GET /media/job/:id Як засіб: якщо пуш не прийшов за розумним часом, запитайте себе про статус. id.
Використання к callback_url
- Тільки
https://. - Проти публічного адресу. Локальні і приватні адреси (
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) ** Відкидаються** це захист від SSRF.
Що вибрати?
| Запитання | Веб-хок | |
|---|---|---|
Публічний https-адрес | Не потрібен | Не потрібен |
| Запізнення отримання | до інтервала опитування | майже мгновенно |
| Гарантія | Ви контролюєте | best-effort + опитування як запас |
| Де зручно | скрипти, CI, ноутбук | сервери, продакшн-бекенди |
** Рекомендація:** на задньому платі веб-хук + опитування як страхування; в скрипті/ноутбуку просто опитування.