Skip to content

Job e webhook

La generazione media è asincrona: ricevi un job ID e interroga lo stato oppure indica callback_url. La firma del webhook consente di verificarne l'origine.

  • Gli stati passano da queued e processing a completed o failed.
  • Il polling è semplice; i webhook richiedono un ricevitore HTTPS pubblico.
  • Gestisci consegne duplicate, verifica la firma e applica idempotenza sul job ID.

Esempi completi

Tutti gli esempi eseguibili della guida originale sono conservati qui sotto. Esegui solo i blocchi adatti al tuo strumento e sistema operativo.

POST /media/generate  →  status: processing  ──►  done   (result_url ready)
        │                                     └─►  failed (error, funds refunded)
        └── (optional) callback_url → push webhook on done/failed
json
{ "id": "beefb531-…", "model": "image/nano-banana-2", "status": "processing",
  "poll": "https://nordrouter.com/media/job/beefb531-…" }
json
{ "id": "beefb531-…", "status": "done",
  "result_url": "https://nordrouter.com/media/file/beefb531-…",
  "cost_usd": 0.052, "error": null }
bash
curl https://nordrouter.com/media/job/beefb531-…
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": "any-string-of-yours"
  }'
json
{ "event": "media.completed", "id": "beefb531-…", "status": "done",
  "result_url": "https://nordrouter.com/media/file/beefb531-…",
  "cost_usd": 0.052, "error": null }
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)  # constant-time compare
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));
}
javascript
import express from "express";
import crypto from "node:crypto";

const SECRET = "any-string-of-yours";          // = your callback_secret
const app = express();

app.post("/webhooks/nordrouter",
  express.raw({ type: "application/json" }),    // raw body — required for the signature
  (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);              // signature mismatch — not from us
    const job = JSON.parse(req.body);
    if (job.event === "media.completed") console.log("done:", job.result_url, "$" + job.cost_usd);
    res.sendStatus(200);                        // reply 2xx or we retry
  });

app.listen(3000);
python
import hmac, hashlib
from fastapi import FastAPI, Request, Response

SECRET = b"any-string-of-yours"                # = your callback_secret
app = FastAPI()

@app.post("/webhooks/nordrouter")
async def hook(request: Request):
    raw = await request.body()                 # raw bytes — required for the signature
    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)       # signature mismatch — not from us
    job = await request.json()
    if job["event"] == "media.completed":
        print("done:", job["result_url"], "$", job["cost_usd"])
    return Response(status_code=200)           # reply 2xx or we retry

Documentazione ufficiale e collegamenti

PollingWebhook
Public https addressnot neededrequired
Latencyup to poll intervalnear-instant
Guaranteeyou control itbest-effort + polling backup
Best forscripts, CI, laptopservers, production backends

Verifica prima la connessione con una richiesta testuale minima, quindi aggiungi streaming, chiamate agli strumenti, immagini e altre funzioni avanzate. Non inserire mai la chiave API in codice pubblico o nel frontend del browser.

Risoluzione dei problemi