EN ZalogujOtwórz Studio
Wersja podglądowa. Katalog modeli, ceny, kalkulator i dokumentacja działają. Generowanie uruchomimy po starcie serwisu.

Webhooki

Dokumentacja: spis stron

Zamiast co chwilę pytać o stan zadania, możesz dać Kadrarisowi adres, pod który sam wyśle wynik. Wpisz webhook_url w POST /v1/generate, a gdy zadanie osiągnie succeeded lub failed, na ten adres przyjdzie zapytanie POST.

Co przychodzi w ciele

Dokładnie ten sam JSON, który zwróciłby GET /v1/tasks/{id}:

json
{
  "id": "task_8f2k...",
  "status": "succeeded",
  "model": "seedance-2.5",
  "output": [{ "type": "video", "url": "https://...",
    "duration": 5, "width": 1280, "height": 720 }],
  "cost": { "amount": "3.10", "currency": "PLN" },
  "error": null,
  "created_at": "2026-09-27T10:15:02Z",
  "completed_at": "2026-09-27T10:16:41Z"
}

Nagłówki

NagłówekZawartość
Content-Typeapplication/json
x-kadraris-eventtask.succeeded lub task.failed
x-kadraris-signaturesha256=<hex> - HMAC-SHA256 z surowego ciała, liczony twoim sekretem webhooka

Sekret webhooka jest w panelu. Traktuj go jak klucz API: tylko na serwerze.

Sprawdzenie podpisu

Kolejność ma znaczenie:

  1. Weź surowe bajty ciała, zanim cokolwiek je sparsuje.
  2. Policz z nich HMAC-SHA256 swoim sekretem.
  3. Porównaj wynik z nagłówkiem funkcją działającą w stałym czasie.
  4. Przy niezgodności odpowiedz 401 i nic dalej nie rób.
python
# pip install flask
import hmac, hashlib, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["KADRARIS_WEBHOOK_SECRET"].encode()
seen = set()  # w produkcji: tabela w bazie

@app.post("/kadraris-hook")
def kadraris_hook():
    raw = request.get_data()  # bajty przed parsowaniem
    digest = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    expected = "sha256=" + digest
    received = request.headers.get("x-kadraris-signature", "")
    if not hmac.compare_digest(expected, received):
        abort(401)

    task = request.get_json()
    event = request.headers.get("x-kadraris-event")
    if task["id"] in seen:
        return "", 200  # to już było
    seen.add(task["id"])

    # twoja funkcja: odłóż pracę do kolejki
    enqueue_processing(task, event)
    return "", 200
javascript
// npm install express
import express from "express";
import crypto from "node:crypto";

const app = express();
const SECRET = process.env.KADRARIS_WEBHOOK_SECRET;
const seen = new Set(); // w produkcji: tabela w bazie

app.post(
  "/kadraris-hook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const expected = "sha256=" + crypto
      .createHmac("sha256", SECRET)
      .update(req.body)
      .digest("hex");
    const received = req.get("x-kadraris-signature") ?? "";
    const ok =
      expected.length === received.length &&
      crypto.timingSafeEqual(
        Buffer.from(expected), Buffer.from(received));
    if (!ok) return res.sendStatus(401);

    const task = JSON.parse(req.body.toString("utf8"));
    if (seen.has(task.id)) return res.sendStatus(200);
    seen.add(task.id);

    // twoja funkcja, uruchomiona po odpowiedzi
    queueMicrotask(() =>
      processTask(task, req.get("x-kadraris-event")));
    res.sendStatus(200);
  },
);

app.listen(3000);

Najczęstsza pułapka to liczenie podpisu z JSON.stringify(req.body) po tym, jak ciało przeszło przez parser. Taki ponownie zserializowany tekst nie jest identyczny z tym, co wysłaliśmy, więc podpis nigdy się nie zgodzi. Podpis zawsze liczysz z oryginalnych bajtów.

Gdy twój serwer nie odbierze

Pierwsza próba wychodzi zaraz po zakończeniu zadania. Jeżeli endpoint nie odpowie kodem 2xx albo milczy, ponawiamy do 5 razy, z coraz dłuższą przerwą:

Próba ponownaPrzerwa od poprzedniej
110 s
21 min
35 min
430 min
52 h

Jeśli i piąta próba się nie uda, powiadomienie przepada. Zadanie nadal odczytasz przez GET /v1/tasks/{id}.

Dobre praktyki po stronie odbiorcy

  • Odpowiadaj od razu. Zapisz treść, a pobieranie plików czy konwersję wrzuć do kolejki. Za długa odpowiedź liczy się jak nieudana i wywołuje ponowienie.
  • Pomijaj powtórki po id zadania. To samo powiadomienie może przyjść dwa razy, np. gdy twoje 200 nie dotarło do nas przez sieć. Zapamiętuj obsłużone id.
  • Ściągaj pliki bez zwłoki. Adresy w output wygasają.
  • Miej zapasową ścieżkę. Po dłuższej awarii, dłuższej niż okno ponowień, odpytaj GET /v1/tasks/{id} o zadania, które nie dały znaku życia.

Test na własnym komputerze

webhook_url musi być widoczny z internetu. Na czas pracy wystaw lokalny port dowolnym tunelem i jego publiczny adres podaj jako webhook_url. Możesz też zasymulować powiadomienie curl-em, podpisując treść samodzielnie:

bash
BODY='{"id":"task_test","status":"succeeded","model":"gpt-image-2.5","output":[],"cost":{"amount":"0.00","currency":"PLN"},"error":null}'
SIG=$(printf '%s' "$BODY" \
  | openssl dgst -sha256 -hmac "$KADRARIS_WEBHOOK_SECRET" \
  | sed 's/^.* //')
curl -X POST http://localhost:3000/kadraris-hook \
  -H "Content-Type: application/json" \
  -H "x-kadraris-event: task.succeeded" \
  -H "x-kadraris-signature: sha256=$SIG" \
  -d "$BODY"