Anleitungen
Webhooks
generation.succeeded- und generation.failed-Webhooks empfangen und den HMAC-SHA256-Header CheaperGen-Signature mit Node.js oder Python prüfen.
Statt den Status abzufragen, übergeben Sie beim Start einer Generierung webhook_url. Sobald der Auftrag abgeschlossen ist, senden wir ein HTTPS-POST mit dem vollständigen Generierungsobjekt an diese URL. Die Anfrage wird mit dem geheimen Schlüssel Ihres Kontos signiert.
Webhook anfordern#
Fügen Sie webhook_url in den Text jeder POST /api/v1/run/{endpoint}-Anfrage ein. Dabei muss es sich um eine HTTPS-URL handeln, die aus dem Internet erreichbar ist.
curl -X POST https://cheapergen.com/api/v1/run/bytedance/seedance-2.0-fast/text-to-video \
-H "Authorization: Bearer $CHEAPERGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Langsame Kamerafahrt auf einen Keramiker, der zur goldenen Stunde eine Vase formt, Staub im Licht, geringe Tiefenschärfe",
"aspect_ratio": "16:9",
"resolution": "720p",
"duration": 5,
"webhook_url": "https://your-app.com/hooks/cheapergen"
}'Ereignisse#
generation.succeededevent- Die Generierung wurde abgeschlossen.
data.outputenthält die Medien-URLs unddata.costden endgültigen Betrag. generation.failedevent- Die Generierung ist fehlgeschlagen.
data.errornennt den Grund (reason,message) und die nächsten Schritte (hint,retryable) – siehe Fehlerursachen; der reservierte Betrag wurde erstattet unddata.cost.amount_usdbeträgt 0.
Bei abgebrochenen Generierungen wird kein Webhook ausgelöst – Sie haben den Auftrag selbst abgebrochen und wissen bereits Bescheid.
Nutzlast#
Der Text ist JSON mit dem Ereignis type und der Generierung im Status data – dasselbe Objekt, das Sie auch über GET /generations/{id} erhalten.
{
"type": "generation.succeeded",
"data": {
"id": "gen_8c2QmX1nq0k4Lw7Z",
"object": "generation",
"model": "bytedance/seedance-2.0-fast",
"endpoint": "bytedance/seedance-2.0-fast/text-to-video",
"mode": "text-to-video",
"status": "succeeded",
"input": {
"prompt": "Langsame Kamerafahrt auf einen Keramiker, der zur goldenen Stunde eine Vase formt, Staub im Licht, geringe Tiefenschärfe",
"aspect_ratio": "16:9",
"resolution": "720p",
"duration": 5,
"generate_audio": true
},
"output": {
"video": {
"url": "https://cdn.cheapergen.com/outputs/gen_8c2QmX1nq0k4Lw7Z.mp4",
"content_type": "video/mp4"
}
},
"error": null,
"cost": {
"amount_usd": 0.4494,
"currency": "usd",
"estimated": false
},
"source": "api",
"created_at": "2026-10-15T09:21:04.512Z",
"started_at": "2026-10-15T09:21:09.031Z",
"completed_at": "2026-10-15T09:22:31.870Z",
"retention": "1d",
"expires_at": "2026-10-16T09:22:31.870Z",
"purged_at": null
}
}Header#
CheaperGen-Signaturestring- Zeitstempel und Signatur:
t=<unix seconds>,v1=<hex HMAC-SHA256>. CheaperGen-Webhook-Idstring- Eine eindeutige ID dieser Benachrichtigung, etwa
whd_3f9c2a…. Sie bleibt bei jedem erneuten Versuch gleich: Deduplizieren Sie anhand dieser ID. Content-Typestringapplication/jsonUser-AgentstringCheaperGen-Webhooks/1.0
Signaturen prüfen#
Prüfen Sie immer die Signatur, bevor Sie einem Webhook vertrauen – an eine öffentliche URL kann jeder POST-Anfragen senden. Der Header sieht so aus:
CheaperGen-Signature: t=1760520151,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd- Teilen Sie den Header anhand von
,auf und lesen Sie die Wertet(Zeitstempel) undv1(Signatur) aus. - Erstellen Sie die signierte Zeichenfolge aus dem Zeitstempel, dem wörtlichen Wert
.und anschließend dem unverändert übernommenen rohen Anfrage-Body. - Berechnen Sie den HMAC-SHA256 dieser Zeichenfolge mit Ihrem Signaturgeheimnis als Schlüssel und geben Sie das Ergebnis hexadezimal aus.
- Vergleichen Sie den Wert mit
v1in konstanter Zeit und lehnen Sie Zeitstempel ab, die mehrere Minuten alt sind, um Replay-Angriffe zu verhindern.
Ihr Signaturschlüssel beginnt mit whsec_ und wird in Dashboard neben Ihren API-Schlüsseln angezeigt. Bewahren Sie ihn wie einen API-Schlüssel ausschließlich auf Ihrem Server auf.
Ihr Code kann ihn auch mit einem API-Schlüssel abrufen: GET /api/v1/webhooks/secret liefert { "key": "whsec_…" }. Hat das Konto noch keinen Schlüssel, erstellt ihn der erste Aufruf.
curl https://cheapergen.com/api/v1/webhooks/secret \
-H "Authorization: Bearer $CHEAPERGEN_API_KEY"Den unveränderten Body verwenden
Prüfen Sie die empfangenen Bytes. Wenn Sie das JSON parsen und erneut serialisieren, ändern sich Leerraum und Schlüsselreihenfolge, sodass die Signatur nicht mehr übereinstimmt.Node.js#
import crypto from "node:crypto";
const TOLERANCE_SECONDS = 300; // reject deliveries older than 5 minutes
/**
* Verify a CheaperGen webhook.
* @param rawBody the request body exactly as received (string or Buffer) — not re-serialized JSON
* @param header the value of the "CheaperGen-Signature" header
* @param secret your signing secret (whsec_…)
*/
export function verifyCheaperGenSignature(rawBody, header, secret) {
const parts = Object.fromEntries(
String(header ?? "")
.split(",")
.map((kv) => kv.trim().split("=", 2)),
);
const t = Number(parts.t);
const v1 = parts.v1;
if (!Number.isFinite(t) || !v1) return false;
if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SECONDS) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(v1, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Python#
import hashlib, hmac, time
TOLERANCE_SECONDS = 300 # reject deliveries older than 5 minutes
def verify_cheapergen_signature(raw_body: bytes, header: str, secret: str) -> bool:
"""Verify a CheaperGen webhook. raw_body must be the exact bytes received."""
try:
parts = dict(kv.strip().split("=", 1) for kv in (header or "").split(","))
t, v1 = int(parts["t"]), parts["v1"]
except (KeyError, ValueError):
return False
if abs(time.time() - t) > TOLERANCE_SECONDS:
return False
signed = f"{t}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)Zustellung und erneute Versuche#
- Antworten Sie innerhalb von 10 Sekunden mit einem beliebigen 2xx-Status, um den Empfang zu bestätigen. Weiterleitungen werden nicht verfolgt.
- Wenn Ihr Endpunkt in ein Timeout läuft oder nicht mit 2xx antwortet, versuchen wir es bis zu einer Stunde lang erneut: etwa 1, 3, 8, 18, 38 und 60 Minuten nach dem ersten Versuch (insgesamt 7 Versuche), danach nicht mehr. Eine
410 Gone-Antwort beendet die Wiederholungen sofort. - Jeder Versuch wird mit einem neuen
tneu signiert, sodass auch ein erneuter Versuch die 5-Minuten-Prüfung besteht wie die erste Zustellung. - Zustellungen können mehrfach und in seltenen Fällen in anderer Reihenfolge eintreffen. Machen Sie Ihren Handler idempotent: Verwenden Sie den Header
CheaperGen-Webhook-Idals Schlüssel (bei jedem erneuten Versuch einer Benachrichtigung gleich) und ignorieren Sie bereits verarbeitete Benachrichtigungen. - Webhooks sind Benachrichtigungen und nicht die einzige maßgebliche Quelle. Wenn Sie eine Benachrichtigung verpassen, können Sie die Generierung jederzeit über
GET /api/v1/generations/{id}abrufen – ein regelmäßiger Abgleich der bei Ihnen noch ausstehenden Aufträge deckt alle Sonderfälle ab.
Lokal testen#
Webhook-URLs müssen öffentlich über HTTPS erreichbar sein. Während der Entwicklung können Sie Ihren lokalen Server über einen Tunnel freigeben (zum Beispiel cloudflared tunnel oder ngrok http 3000) und dessen HTTPS-URL als webhook_url verwenden.
Ist etwas unklar oder fehlt etwas? Schreiben Sie an support@cheapergen.com – wir lesen jede Nachricht.