Webhooks

Werden Sie benachrichtigt, sobald eine asynchrone Generierungsaufgabe abgeschlossen ist — statt zu pollen

Überblick

Wenn Sie eine asynchrone Generierungsaufgabe einreichen, verarbeitet Atlas Cloud sie im Hintergrund, und das Ergebnis wird einige Zeit später verfügbar. Anstatt wiederholt den Vorhersagen-Endpunkt aufzurufen, bis die Aufgabe abgeschlossen ist, können Sie Atlas Cloud bitten, Sie zurückzurufen, sobald die Aufgabe einen Endzustand erreicht.

Dazu geben Sie beim Einreichen der Aufgabe eine webhook_url an. Wenn die Aufgabe abgeschlossen ist — egal ob sie erfolgreich war, fehlgeschlagen ist oder ein Timeout hatte — sendet Atlas Cloud einen einzelnen signierten POST an diese URL, der das Endergebnis enthält.

Unterstützte Aufgabentypen

Webhooks sind für die asynchrone Generierung von Video, Bild und Audio verfügbar. Die Zustell-Engine ist unabhängig vom Aufgabentyp — der event_type (und der Header X-AtlasCloud-Webhook-Event) gibt die Modalität an: video.task.terminal, image.task.terminal oder audio.task.terminal.

Webhooks ergänzen das Polling — sie ersetzen es nicht. Der Vorhersagen-Endpunkt funktioniert weiterhin genau wie zuvor, und eine Webhook-Payload enthält dieselbe Ergebnisstruktur, die Sie per Polling erhalten hätten. Nutzen Sie das eine, das andere oder beides.

Schnellstart

Fügen Sie Ihrer bestehenden Einreichungsanfrage ein Feld webhook_url hinzu:

curl -X POST https://api.atlascloud.ai/api/v1/model/generateVideo \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
        "model": "bytedance/seedance-2.0/text-to-video",
        "prompt": "A calico kitten chasing a butterfly in a garden, cinematic",
        "duration": 5,
        "resolution": "1080p",
        "webhook_url": "https://your-app.example.com/hooks/atlascloud"
      }'
import requests

response = requests.post(
    "https://api.atlascloud.ai/api/v1/model/generateVideo",
    headers={
        "Authorization": "Bearer your-api-key",
        "Content-Type": "application/json",
    },
    json={
        "model": "bytedance/seedance-2.0/text-to-video",
        "prompt": "A calico kitten chasing a butterfly in a garden, cinematic",
        "duration": 5,
        "resolution": "1080p",
        "webhook_url": "https://your-app.example.com/hooks/atlascloud",
    },
)

print(response.json()["data"]["id"])  # die Aufgaben-ID (session_id)
const res = await fetch("https://api.atlascloud.ai/api/v1/model/generateVideo", {
  method: "POST",
  headers: {
    Authorization: "Bearer your-api-key",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "bytedance/seedance-2.0/text-to-video",
    prompt: "A calico kitten chasing a butterfly in a garden, cinematic",
    duration: 5,
    resolution: "1080p",
    webhook_url: "https://your-app.example.com/hooks/atlascloud",
  }),
});

const { data } = await res.json();
console.log(data.id); // die Aufgaben-ID (session_id)

Dasselbe Feld webhook_url funktioniert identisch bei POST /api/v1/model/generateImage (asynchrones Bild, event_type: "image.task.terminal") und POST /api/v1/model/generateAudio (asynchrones Audio, event_type: "audio.task.terminal").

Die Antwort auf die Einreichung bleibt unverändert — Sie erhalten weiterhin sofort eine Aufgaben-id (die session_id) zurück. Die webhook_url wird von Atlas Cloud verarbeitet und niemals an den vorgelagerten Modellanbieter weitergeleitet. Sobald die Aufgabe abgeschlossen ist, empfängt Ihr Endpunkt einen POST.

Anforderungen an webhook_url

Ihre Callback-URL wird beim Einreichen validiert. Schlägt die Validierung fehl, wird die Einreichungsanfrage mit HTTP 400 abgelehnt und es wird keine Aufgabe erstellt (es entstehen Ihnen keine Kosten).

RegelDetail
SchemaMuss https:// sein. Reines http:// wird abgelehnt.
HostMuss eine routbare, öffentliche Adresse sein. Private, Loopback-, Link-Local- und CGNAT-Bereiche (100.64.0.0/10) werden abgelehnt.
LängeMaximal 1024 Zeichen.
ErreichbarkeitMuss aus dem öffentlichen Internet erreichbar sein, damit Atlas Cloud einen POST senden kann.

Diese Prüfungen dienen dem SSRF-Schutz. Verwenden Sie für die lokale Entwicklung einen öffentlichen Tunnel (einen Webhook-Testdienst, ngrok oder einen Cloudflare-Tunnel) statt einer privaten Adresse.

Die Callback-Anfrage

Sobald die Aufgabe einen Endzustand erreicht, sendet Atlas Cloud einen POST mit Content-Type: application/json und den folgenden Headern:

HeaderBeschreibung
X-AtlasCloud-Webhook-IdDie session_id der Aufgabe — Ihr Korrelations- und Idempotenzschlüssel.
X-AtlasCloud-Webhook-EventDer Ereignistyp, z. B. video.task.terminal, image.task.terminal oder audio.task.terminal.
X-AtlasCloud-Webhook-TimestampUnix-Epoch-Sekunden zum Zeitpunkt des Zustellversuchs. Wird von der Ed25519-Signatur abgedeckt.
X-AtlasCloud-Webhook-SignatureHex-kodierter HMAC-SHA256 des rohen Anfrage-Bodys (HMAC-Verfahren). Beim reinen Ed25519-Verfahren enthält dieser Header stattdessen die Ed25519-Signatur — siehe Signaturen verifizieren.
X-AtlasCloud-Webhook-Signature-Ed25519base64url-kodierte Ed25519-Signatur über <timestamp>.<raw_body> (wird während der Migration von HMAC zu Ed25519 gesendet).
X-AtlasCloud-Webhook-Key-IdDie kid des Ed25519-Signaturschlüssels — entspricht einem Schlüssel im JWKS.
User-AgentAtlasCloud-Webhook/1.0

Payload

{
  "session_id": "string",       // die Aufgaben-ID; Ihr Idempotenzschlüssel
  "event_type": "string",       // z. B. "video.task.terminal"
  "status": "OK" | "ERROR",     // Gesamtergebnis — hierauf verzweigen
  "created_at": 1782295062952,  // Erstellungszeit der Aufgabe (ms seit Epoch)
  "payload": {                  // das Ergebnis, gleiche Struktur wie in der Predictions-API
    "model": "string",
    "status": "completed" | "failed" | "timeout",
    "outputs": ["https://..."], // bei Erfolg vorhanden
    "error_code": 0             // bei Fehlschlag vorhanden
  },
  "error": "string"             // nur vorhanden, wenn status == "ERROR"
}

Verzweigen Sie Ihren Handler anhand des Feldes status auf oberster Ebene: OK bedeutet, dass ein verwertbares Ergebnis in payload.outputs liegt; ERROR bedeutet, dass die Aufgabe kein Ergebnis erzeugt hat und error den Grund erklärt.

Ergebnisse der Audiotranskription

Text-to-Speech und andere generative Audioaufgaben liefern die URLs ihrer Audiodateien in payload.outputs, genau wie Video und Bild. Bei Modellen für Speech-to-Text (Transkription) enthält ein abgeschlossenes payload zusätzlich ein strukturiertes stt_result-Objekt (vollständiger Text, erkannte Sprache und Zeitstempel auf Wortebene) — dasselbe Feld, das auch der Vorhersagen-Endpunkt zurückgibt.

Beispiel für Erfolg

Eine echte Zustellung für eine abgeschlossene Aufgabe des Modells bytedance/seedance-2.0/text-to-video:

{
  "session_id": "6a0c02cdb4b147b7bc78881eb7229ece",
  "event_type": "video.task.terminal",
  "status": "OK",
  "created_at": 1782295062952,
  "payload": {
    "model": "bytedance/seedance-2.0/text-to-video",
    "status": "completed",
    "outputs": [
      "https://atlas-media.oss-us-west-1.aliyuncs.com/videos/cgt-20260624-0.mp4"
    ]
  }
}

Beispiel für einen Fehlschlag

{
  "session_id": "9b2f4e7a1c0d4f5e8a6b3c2d1e0f9a8b",
  "event_type": "video.task.terminal",
  "status": "ERROR",
  "created_at": 1782200000000,
  "payload": {
    "model": "bytedance/seedance-2.0/text-to-video",
    "status": "failed",
    "error_code": 1039
  },
  "error": "the input was rejected by content moderation"
}

Ein timeout-Ergebnis sieht genauso aus, mit payload.status: "timeout" und einer generischen error-Meldung.

Signaturen verifizieren

Verifizieren Sie die Signatur immer, bevor Sie einem Webhook vertrauen. Sie belegt, dass die Anfrage von Atlas Cloud stammt und nicht manipuliert wurde.

Zwei Verfahren während der Migration

Atlas Cloud stellt die Webhook-Signaturen von einem gemeinsamen HMAC-Geheimnis auf Ed25519 mit einem öffentlichen JWKS-Endpunkt um. Während des Übergangs enthalten Zustellungen sowohl eine HMAC-Signatur (X-AtlasCloud-Webhook-Signature) als auch eine Ed25519-Signatur (X-AtlasCloud-Webhook-Signature-Ed25519). Bevorzugen Sie Ed25519 — die Verifizierung erfolgt gegen einen öffentlichen Schlüssel, den Sie von einer URL abrufen, ohne dass Sie ein gemeinsames Geheimnis speichern müssen.

Ed25519 + JWKS (empfohlen)

Atlas Cloud signiert jede Zustellung mit einem privaten Ed25519-Schlüssel und veröffentlicht den passenden öffentlichen Schlüssel an einem JWKS-Endpunkt. Sie verifizieren gegen diesen öffentlichen Schlüssel — auf Ihrer Seite muss kein Geheimnis bereitgestellt oder rotiert werden.

  • Öffentliche Schlüssel (JWKS): GET https://api.atlascloud.ai/api/v1/webhooks/jwks.json (ohne Authentifizierung):
{
  "keys": [
    {
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "Wn_rgZDBO6nv4-ka97PPf5z8WXbM25o75dR6YukTqqI",
      "use": "sig",
      "alg": "EdDSA",
      "kid": "cnut8IN2blKoB5zRJr9th0HoLzH-iBH3WYoUtkcJZQE"
    }
  ]
}
  • Signierte Nachricht: "<timestamp>.<raw_body>" — der Wert von X-AtlasCloud-Webhook-Timestamp, ein wörtlicher ., dann der exakte rohe Anfrage-Body. Anders als bei HMAC ist der Zeitstempel von der Signatur abgedeckt, sodass Sie ein Replay-Fenster erzwingen können.
  • Signatur-Header: X-AtlasCloud-Webhook-Signature-Ed25519 (base64url). Sobald HMAC abgeschaltet ist, wandert die Ed25519-Signatur nach X-AtlasCloud-Webhook-Signature — bevorzugen Sie daher den -Ed25519-Header, wenn er vorhanden ist, und greifen Sie sonst auf -Signature zurück.
  • Schlüssel-ID: X-AtlasCloud-Webhook-Key-Id ist die kid des JWK, mit dem die Zustellung signiert wurde.

Schritte

  1. Lesen Sie X-AtlasCloud-Webhook-Timestamp (ts), X-AtlasCloud-Webhook-Key-Id (kid) und den Ed25519-Signatur-Header aus.
  2. (Empfohlen) Weisen Sie die Anfrage ab, wenn der Zeitstempel mehr als ca. 5 Minuten von Ihrer Uhr abweicht (Replay-Schutz).
  3. Rufen Sie das JWKS ab und wählen Sie den Schlüssel mit passender kid. Cachen Sie das JWKS; bei einer kid, die Sie nicht kennen, rufen Sie es einmal erneut ab (der Signaturschlüssel könnte rotiert worden sein).
  4. Dekodieren Sie das JWK-Feld x (base64url) → der 32 Byte lange öffentliche Ed25519-Schlüssel.
  5. Verifizieren Sie die base64url-dekodierte Signatur über ts + "." + raw_body.
const crypto = require("crypto");

const JWKS_URL = "https://api.atlascloud.ai/api/v1/webhooks/jwks.json";
let jwks = {}; // kid -> jwk

async function publicKey(kid) {
  if (!jwks[kid]) {
    const { keys } = await (await fetch(JWKS_URL)).json();
    jwks = Object.fromEntries(keys.map((k) => [k.kid, k]));
  }
  const jwk = jwks[kid];
  return jwk && crypto.createPublicKey({ key: jwk, format: "jwk" });
}

// req.body muss der ROHE Body-Buffer sein (z. B. express.raw()).
async function verifyEd25519(req) {
  const ts = req.get("X-AtlasCloud-Webhook-Timestamp");
  const kid = req.get("X-AtlasCloud-Webhook-Key-Id");
  const sig =
    req.get("X-AtlasCloud-Webhook-Signature-Ed25519") ||
    req.get("X-AtlasCloud-Webhook-Signature");

  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // Replay-Fenster

  const pub = await publicKey(kid);
  if (!pub) return false;
  const msg = Buffer.concat([Buffer.from(`${ts}.`), req.body]);
  return crypto.verify(null, msg, pub, Buffer.from(sig, "base64url"));
}
import time, json, base64, urllib.request
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.exceptions import InvalidSignature

JWKS_URL = "https://api.atlascloud.ai/api/v1/webhooks/jwks.json"
_jwks = {}  # kid -> jwk

def _b64u(s: str) -> bytes:
    return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))

def _public_key(kid: str):
    if kid not in _jwks:
        keys = json.load(urllib.request.urlopen(JWKS_URL, timeout=5))["keys"]
        _jwks.clear()
        _jwks.update({k["kid"]: k for k in keys})
    jwk = _jwks.get(kid)
    return Ed25519PublicKey.from_public_bytes(_b64u(jwk["x"])) if jwk else None

def verify_ed25519(headers, raw_body: bytes) -> bool:
    ts = headers["X-AtlasCloud-Webhook-Timestamp"]
    kid = headers["X-AtlasCloud-Webhook-Key-Id"]
    sig = headers.get("X-AtlasCloud-Webhook-Signature-Ed25519") \
        or headers["X-AtlasCloud-Webhook-Signature"]

    if abs(time.time() - int(ts)) > 300:  # Replay-Fenster
        return False
    pub = _public_key(kid)
    if pub is None:
        return False
    try:
        pub.verify(_b64u(sig), ts.encode() + b"." + raw_body)
        return True
    except InvalidSignature:
        return False
const jwksURL = "https://api.atlascloud.ai/api/v1/webhooks/jwks.json"

// fetchKey liefert den base64url-kodierten öffentlichen Schlüssel zur kid. Ergänzen
// Sie im Produktivcode Caching + einen einmaligen erneuten Abruf bei Fehltreffer
// (Schlüsselrotation).
func fetchKey(kid string) (string, bool) {
    resp, err := http.Get(jwksURL)
    if err != nil {
        return "", false
    }
    defer resp.Body.Close()
    var set struct {
        Keys []struct{ Kid, X string } `json:"keys"`
    }
    if json.NewDecoder(resp.Body).Decode(&set) != nil {
        return "", false
    }
    for _, k := range set.Keys {
        if k.Kid == kid {
            return k.X, true
        }
    }
    return "", false
}

func verifyEd25519(h http.Header, body []byte) bool {
    ts := h.Get("X-AtlasCloud-Webhook-Timestamp")
    sigB64 := h.Get("X-AtlasCloud-Webhook-Signature-Ed25519")
    if sigB64 == "" {
        sigB64 = h.Get("X-AtlasCloud-Webhook-Signature")
    }
    t, _ := strconv.ParseInt(ts, 10, 64)
    if math.Abs(float64(time.Now().Unix()-t)) > 300 { // Replay-Fenster
        return false
    }
    xB64, ok := fetchKey(h.Get("X-AtlasCloud-Webhook-Key-Id"))
    if !ok {
        return false
    }
    pub, e1 := base64.RawURLEncoding.DecodeString(xB64)
    sig, e2 := base64.RawURLEncoding.DecodeString(sigB64)
    if e1 != nil || e2 != nil || len(pub) != ed25519.PublicKeySize {
        return false
    }
    return ed25519.Verify(ed25519.PublicKey(pub), append([]byte(ts+"."), body...), sig)
}

Die Signatur belegt, dass der Callback von Atlas Cloud stammt — nicht, welchem Konto die Aufgabe gehört. Da die webhook_url pro Anfrage angegeben wird, sollten Sie die session_id zusätzlich einer Aufgabe zuordnen, die Sie tatsächlich erstellt haben, bevor Sie auf das Ergebnis reagieren.

HMAC (veraltet)

Die HMAC-Signierung wird zugunsten von Ed25519/JWKS (siehe oben) eingestellt; neue Integrationen sollten Ed25519 verwenden. Der HMAC-Header X-AtlasCloud-Webhook-Signature wird während des Migrationsfensters weiterhin gesendet.

Die Signatur wird wie folgt berechnet:

HMAC-SHA256( signing_secret, raw_request_body )   →   lowercase hex
  • Der Schlüssel ist Ihr gemeinsames Signaturgeheimnis (für Ihr Konto bereitgestellt).
  • Die Nachricht sind die exakten rohen Bytes des Anfrage-Bodys — verifizieren Sie vor jeder erneuten JSON-Serialisierung, die Byte-Reihenfolge oder Leerzeichen verändern könnte.
  • Vergleichen Sie das Ergebnis mit dem Header X-AtlasCloud-Webhook-Signature über einen zeitkonstanten Vergleich.

Der Header X-AtlasCloud-Webhook-Timestamp dient nur zur Information (nützlich für eine optionale Prüfung des Replay-Fensters). Er ist nicht Teil des signierten Inhalts — signiert wird ausschließlich der rohe Body.

const crypto = require("crypto");
const express = require("express");
const app = express();

const SIGNING_SECRET = process.env.ATLASCLOUD_WEBHOOK_SECRET;

// Den ROHEN Body erfassen — kein JSON-Parser darf vorher laufen.
app.use("/hooks/atlascloud", express.raw({ type: "*/*" }));

app.post("/hooks/atlascloud", (req, res) => {
  const sig = req.get("X-AtlasCloud-Webhook-Signature") || "";
  const expected = crypto
    .createHmac("sha256", SIGNING_SECRET)
    .update(req.body)               // req.body ist ein Buffer (rohe Bytes)
    .digest("hex");

  // Als Buffer vergleichen und die BYTE-Länge prüfen: timingSafeEqual wirft bei
  // Eingaben ungleicher Länge eine Ausnahme, und ein fehlerhafter Multibyte-Header
  // kann in der JS-Stringlänge übereinstimmen, in der Byte-Länge aber abweichen.
  const sigBuf = Buffer.from(sig);
  const expBuf = Buffer.from(expected);
  const ok =
    sigBuf.length === expBuf.length &&
    crypto.timingSafeEqual(sigBuf, expBuf);
  if (!ok) return res.status(401).send("invalid signature");

  const event = JSON.parse(req.body.toString("utf8"));
  // ... nach event.session_id in die Queue stellen, dann schnell antworten ...
  res.status(200).send("ok");
});
import hmac, hashlib, os
from flask import Flask, request, abort

SIGNING_SECRET = os.environ["ATLASCLOUD_WEBHOOK_SECRET"].encode()
app = Flask(__name__)

@app.post("/hooks/atlascloud")
def atlascloud_webhook():
    raw = request.get_data()  # rohe Bytes, vor dem JSON-Parsing
    expected = hmac.new(SIGNING_SECRET, raw, hashlib.sha256).hexdigest()
    received = request.headers.get("X-AtlasCloud-Webhook-Signature", "")
    # Als Bytes vergleichen: compare_digest wirft bei str mit Nicht-ASCII eine Ausnahme.
    if not hmac.compare_digest(expected.encode(), received.encode()):
        abort(401)

    event = request.get_json()
    # ... nach event["session_id"] in die Queue stellen, dann schnell 200 antworten ...
    return "ok", 200
func verify(secret string, body []byte, sigHeader string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(sigHeader))
}

Zustellsemantik

Eine Zustellung bestätigen

Antworten Sie mit einem beliebigen 2xx-Statuscode, um den Empfang zu bestätigen. Jeder andere Statuscode — oder eine Zeitüberschreitung der Verbindung — gilt als Fehlschlag, und die Zustellung wird wiederholt.

Antworten Sie schnell (deutlich innerhalb weniger Sekunden). Erledigen Sie die eigentliche Arbeit asynchron: Signatur verifizieren, das Ereignis unter dem Schlüssel session_id in eine Queue stellen und sofort 200 zurückgeben. Langsame Antworten laufen Gefahr, in eine Zeitüberschreitung zu laufen und unnötige Wiederholungen auszulösen.

Wiederholungen und Backoff

Wird eine Zustellung nicht bestätigt, wiederholt Atlas Cloud sie mit exponentiellem Backoff (etwa 10s → 20s → 40s → …, begrenzt auf rund 30 Minuten), bis zu ca. 10 Versuchen. Nach Ausschöpfen der Versuche wird die Zustellung als unzustellbar markiert und nicht weiter wiederholt.

At-least-once — Deduplizierung über session_id

Die Zustellung erfolgt mindestens einmal (at-least-once). In seltenen Fällen erhalten Sie denselben Webhook mehrfach. Gestalten Sie Ihren Handler idempotent und deduplizieren Sie über session_id.

Die session_id bleibt über Wiederholungen hinweg stabil. Behandeln Sie einen Webhook für eine session_id, die Sie bereits vollständig verarbeitet haben, als No-Op und geben Sie 200 zurück.

Zeitverhalten

Die allermeisten Webhooks werden innerhalb von Sekunden nach Abschluss der Aufgabe zugestellt. Ein eingebautes Abgleichsnetz garantiert die Zustellung auch dann, wenn der schnelle Pfad verpasst wird (etwa während einer Service-Bereitstellung) — in diesen seltenen Fällen um den Preis einer Verzögerung von bis zu ca. 30 Minuten. Planen Sie für eine letztlich erfolgende Zustellung nach dem At-least-once-Prinzip statt für sofortige Exactly-once-Zustellung.

Best Practices

  • Stellen Sie den Callback über HTTPS auf einem öffentlich erreichbaren Host bereit.
  • Verifizieren Sie die Webhook-Signatur, bevor Sie dem Ereignis vertrauen — vorzugsweise mit Ed25519/JWKS (kein Geheimnis zu speichern; Verifizierung gegen den über kid abgerufenen öffentlichen Schlüssel). HMAC ist der veraltete Rückfallweg während des Migrationsfensters.
  • Beim Verifizieren mit Ed25519: Cachen Sie das JWKS und rufen Sie es bei unbekannter kid erneut ab; prüfen Sie die Signatur über "<timestamp>.<raw_body>" und erzwingen Sie ein Replay-Fenster.
  • Antworten Sie schnell mit 2xx; verlagern Sie die eigentliche Verarbeitung in eine Hintergrund-Queue.
  • Deduplizieren Sie über session_id — Handler müssen idempotent sein.
  • Verzweigen Sie anhand des status auf oberster Ebene (OK vs. ERROR); lesen Sie Ergebnisse aus payload.outputs.
  • Setzen Sie weder eine bestimmte Reihenfolge noch Exactly-once voraus; planen Sie für At-least-once.
  • Behalten Sie den Vorhersagen-Endpunkt als Rückfall- bzw. Abgleichspfad bei.
  • Nur beim veralteten HMAC: Halten Sie Ihr Signaturgeheimnis vertraulich und rotieren Sie es, falls es offengelegt wurde. (Bei Ed25519/JWKS gibt es auf Ihrer Seite kein Geheimnis.)

Fehlerbehebung

SymptomWahrscheinliche Ursache / Maßnahme
Einreichung liefert 400 webhook_url ... is not a routable public addressDer Host ist privat/Loopback/CGNAT. Verwenden Sie eine öffentliche HTTPS-URL oder einen Tunnel.
Einreichung liefert 400 webhook_url must use httpsStellen Sie das Schema auf https:// um.
Einreichung liefert 400 webhook_url exceeds the 1024-character limitKürzen Sie die URL (verlagern Sie den Zustand in Ihren eigenen Speicher, indiziert über session_id).
Kein Webhook empfangenPrüfen Sie, ob der Endpunkt öffentlich erreichbar ist und 2xx zurückgibt; verifizieren Sie über den Vorhersagen-Endpunkt, dass die Aufgabe tatsächlich einen Endzustand erreicht hat.
Signatur stimmt nicht überein (Ed25519)Signieren Sie über "<timestamp>.<raw_body>" (nicht nur über den Body), dekodieren Sie die Signatur mit base64url und suchen Sie den Schlüssel über die kid im JWKS.
Signatur stimmt nicht überein (HMAC)Stellen Sie sicher, dass Sie den HMAC über die rohen Body-Bytes bilden (nicht über ein neu serialisiertes JSON-Objekt) und das richtige Signaturgeheimnis verwenden.
kid nicht im JWKS enthaltenDer Signaturschlüssel wurde rotiert — rufen Sie das JWKS erneut ab (cachen Sie nicht dauerhaft einen einzelnen Schlüssel).
Dasselbe Ereignis zweimal empfangenBei At-least-once-Zustellung zu erwarten — deduplizieren Sie über session_id.

Referenz

  • Einreichen (mit Webhook): POST /api/v1/model/generateVideo, POST /api/v1/model/generateImage oder POST /api/v1/model/generateAudio — fügen Sie webhook_url hinzu.
  • Ereignistypen: video.task.terminal, image.task.terminal, audio.task.terminal.
  • JWKS (öffentliche Schlüssel): GET /api/v1/webhooks/jwks.json.
  • Signatur (Ed25519, empfohlen): base64url-kodiertes Ed25519 über "<timestamp>.<raw_body>", im Header X-AtlasCloud-Webhook-Signature-Ed25519 (Schlüssel-ID in X-AtlasCloud-Webhook-Key-Id).
  • Signatur (HMAC, veraltet): HMAC-SHA256(signing_secret, raw_body), hex-kodiert, im Header X-AtlasCloud-Webhook-Signature.
  • Idempotenzschlüssel: session_id (auch in X-AtlasCloud-Webhook-Id).
  • Alternative per Polling: Vorhersagen.