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).
| Regel | Detail |
|---|---|
| Schema | Muss https:// sein. Reines http:// wird abgelehnt. |
| Host | Muss eine routbare, öffentliche Adresse sein. Private, Loopback-, Link-Local- und CGNAT-Bereiche (100.64.0.0/10) werden abgelehnt. |
| Länge | Maximal 1024 Zeichen. |
| Erreichbarkeit | Muss 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:
| Header | Beschreibung |
|---|---|
X-AtlasCloud-Webhook-Id | Die session_id der Aufgabe — Ihr Korrelations- und Idempotenzschlüssel. |
X-AtlasCloud-Webhook-Event | Der Ereignistyp, z. B. video.task.terminal, image.task.terminal oder audio.task.terminal. |
X-AtlasCloud-Webhook-Timestamp | Unix-Epoch-Sekunden zum Zeitpunkt des Zustellversuchs. Wird von der Ed25519-Signatur abgedeckt. |
X-AtlasCloud-Webhook-Signature | Hex-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-Ed25519 | base64url-kodierte Ed25519-Signatur über <timestamp>.<raw_body> (wird während der Migration von HMAC zu Ed25519 gesendet). |
X-AtlasCloud-Webhook-Key-Id | Die kid des Ed25519-Signaturschlüssels — entspricht einem Schlüssel im JWKS. |
User-Agent | AtlasCloud-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 vonX-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 nachX-AtlasCloud-Webhook-Signature— bevorzugen Sie daher den-Ed25519-Header, wenn er vorhanden ist, und greifen Sie sonst auf-Signaturezurück. - Schlüssel-ID:
X-AtlasCloud-Webhook-Key-Idist diekiddes JWK, mit dem die Zustellung signiert wurde.
Schritte
- Lesen Sie
X-AtlasCloud-Webhook-Timestamp(ts),X-AtlasCloud-Webhook-Key-Id(kid) und den Ed25519-Signatur-Header aus. - (Empfohlen) Weisen Sie die Anfrage ab, wenn der Zeitstempel mehr als ca. 5 Minuten von Ihrer Uhr abweicht (Replay-Schutz).
- Rufen Sie das JWKS ab und wählen Sie den Schlüssel mit passender
kid. Cachen Sie das JWKS; bei einerkid, die Sie nicht kennen, rufen Sie es einmal erneut ab (der Signaturschlüssel könnte rotiert worden sein). - Dekodieren Sie das JWK-Feld
x(base64url) → der 32 Byte lange öffentliche Ed25519-Schlüssel. - 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 Falseconst 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", 200func 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
kidabgerufenen ö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
kiderneut 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
statusauf oberster Ebene (OKvs.ERROR); lesen Sie Ergebnisse auspayload.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
| Symptom | Wahrscheinliche Ursache / Maßnahme |
|---|---|
Einreichung liefert 400 webhook_url ... is not a routable public address | Der Host ist privat/Loopback/CGNAT. Verwenden Sie eine öffentliche HTTPS-URL oder einen Tunnel. |
Einreichung liefert 400 webhook_url must use https | Stellen Sie das Schema auf https:// um. |
Einreichung liefert 400 webhook_url exceeds the 1024-character limit | Kürzen Sie die URL (verlagern Sie den Zustand in Ihren eigenen Speicher, indiziert über session_id). |
| Kein Webhook empfangen | Prü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 enthalten | Der Signaturschlüssel wurde rotiert — rufen Sie das JWKS erneut ab (cachen Sie nicht dauerhaft einen einzelnen Schlüssel). |
| Dasselbe Ereignis zweimal empfangen | Bei At-least-once-Zustellung zu erwarten — deduplizieren Sie über session_id. |
Referenz
- Einreichen (mit Webhook):
POST /api/v1/model/generateVideo,POST /api/v1/model/generateImageoderPOST /api/v1/model/generateAudio— fügen Siewebhook_urlhinzu. - 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 HeaderX-AtlasCloud-Webhook-Signature-Ed25519(Schlüssel-ID inX-AtlasCloud-Webhook-Key-Id). - Signatur (HMAC, veraltet):
HMAC-SHA256(signing_secret, raw_body), hex-kodiert, im HeaderX-AtlasCloud-Webhook-Signature. - Idempotenzschlüssel:
session_id(auch inX-AtlasCloud-Webhook-Id). - Alternative per Polling: Vorhersagen.