Plattform

Callbacks (Webhooks)

Statt den Status abzufragen, gib bei einer Bild- oder Videoanfrage eine callback_url an. Sobald das Ergebnis bereit ist oder ein Fehler auftritt, sendet Zurelay es per POST an diese URL – mit einer Signatur, an der du erkennst, dass es von uns kommt.

Callback anfordern

Füge einer dieser Anfragen callback_url hinzu. Die URL muss öffentlich erreichbar sein und https verwenden.

EndpunktWas passiert
POST /v1/videosStartet das Video wie gewohnt. Wenn der Auftrag abgeschlossen ist, liefert der Callback das fertige Video oder den Fehlergrund. Du musst den Status nicht abfragen.
POST /v1/images/generationsAntwortet sofort mit 202 und der Anfrage-ID, statt auf die Bilder zu warten. Die Bilder werden im Hintergrund erstellt und anschließend per Callback gesendet.
POST /v1/images/editsDasselbe gilt für Bildbearbeitungen: als JSON-Feld oder als Formularfeld in einer multipart-Anfrage.
curl https://api.zurelay.com/v1/videos \
-H "Authorization: Bearer $ZURELAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2.0",
"prompt": "A fox running through fresh snow at dawn",
"seconds": 5,
"callback_url": "https://example.com/hooks/zurelay"
}'

Bildanfragen mit Callback

Ohne callback_url wartet eine Bildanfrage, bis die Bilder fertig sind, und gibt sie dann zurück – wie bei der API von OpenAI. Mit dieser Option wird die Anfrage sofort angenommen und der Job zurückgegeben. Kosten fallen nur an, wenn Bilder erstellt werden:

Antwort
HTTP/1.1 202 Accepted
x-request-id: req_6fJd0kQ3pW9xT2mB7cZ1hR4u
{
"id": "req_6fJd0kQ3pW9xT2mB7cZ1hR4u",
"object": "image.generation",
"model": "nano-banana-pro",
"status": "in_progress",
"created_at": 1791000000,
"completed_at": null,
"data": [],
"url_expires_at": null,
"cost": null,
"error": null,
"callback_url": "https://example.com/hooks/zurelay"
}
GET/v1/images/{id}

Du kannst den Status auch abfragen: GET /v1/images/{id} gibt dasselbe Objekt zurück, mit aktuellen Links in data, sobald status den Wert completed hat. Callbacks enthalten immer Links, nie base64 – unabhängig vom Wert von response_format.

Was wir senden

Ein POST pro Ereignis mit einem JSON-Body: type, timestamp und data. Der Auftrag entspricht genau der API-Antwort (das Video-Objekt oder die obige Bildanfrage) und enthält Download-Links, die 24 Stunden gültig sind.

EreignisWann
video.completedDas Video ist fertig: Über data.url kannst du es herunterladen.
video.failedDas Video konnte nicht erstellt werden: data.error nennt den Grund. Dir wurde nichts berechnet.
image.completedDie Bilder sind fertig: data.data enthält einen Link pro Bild.
image.failedEs wurde kein Bild erstellt: data.error nennt den Grund. Dir wurde nichts berechnet.
Ein Video-Callback
POST /hooks/zurelay HTTP/1.1
content-type: application/json
user-agent: Zurelay-Webhooks/1.0 (+https://zurelay.com/docs/webhooks)
webhook-id: msg_2c1f9e0b7a4d4f6e8b3a91c05d7e6f42
webhook-timestamp: 1791000094
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"type": "video.completed",
"timestamp": "2026-10-03T03:21:34.000Z",
"data": {
"id": "video_Qm3v8Rk2Lx7Pa1Zt9Ws4Yn6B",
"object": "video",
"model": "seedance-2.0",
"status": "completed",
"progress": 100,
"created_at": 1791000000,
"completed_at": 1791000093,
"expires_at": 1793592000,
"seconds": 5,
"resolution": "720p",
"aspect_ratio": "16:9",
"size": "1280x720",
"generate_audio": true,
"url": "https://…/video_Qm3v8Rk2Lx7Pa1Zt9Ws4Yn6B.mp4?token=…",
"url_expires_at": 1791086494,
"cost": 0.45,
"error": null,
"callback_url": "https://example.com/hooks/zurelay"
}
}
Ein Bild-Callback (Body)
{
"type": "image.completed",
"timestamp": "2026-10-03T03:20:41.000Z",
"data": {
"id": "req_6fJd0kQ3pW9xT2mB7cZ1hR4u",
"object": "image.generation",
"model": "nano-banana-pro",
"status": "completed",
"created_at": 1791000000,
"completed_at": 1791000041,
"data": [{ "url": "https://…/req_6fJd0kQ3pW9xT2mB7cZ1hR4u-1.png?token=…" }],
"url_expires_at": 1791086441,
"cost": 0.07,
"error": null,
"callback_url": "https://example.com/hooks/zurelay"
}
}
HeaderWert
webhook-idmsg_…: Bleibt bei jedem erneuten Versuch für diesen Callback gleich. Damit kannst du bereits verarbeitete Callbacks überspringen.
webhook-timestampZeitpunkt, zu dem dieser Versuch gesendet wurde, in Unix-Sekunden.
webhook-signaturev1, gefolgt von der Signatur (siehe unten). Nach dem Wechsel deines Secrets werden einen Tag lang zwei Signaturen gesendet, durch ein Leerzeichen getrennt.
user-agentZurelay-Webhooks/1.0

Signatur prüfen

Callbacks folgen dem Format Standard Webhooks, das auch die Webhooks von OpenAI verwenden. Die Signatur ist ein HMAC-SHA256 über <webhook-id>.<webhook-timestamp>.<body> mit dem Signatur-Secret deines Workspaces als Schlüssel, codiert in base64. Du findest das Secret (es beginnt mit whsec_) im Dashboard unter API-Schlüssel. Bewahre es nur auf deinem Server auf.

import os
from fastapi import FastAPI, HTTPException, Request
from standardwebhooks.webhooks import Webhook # pip install standardwebhooks
webhook = Webhook(os.environ["ZURELAY_WEBHOOK_SECRET"]) # whsec_…
app = FastAPI()
@app.post("/hooks/zurelay")
async def zurelay_hook(request: Request):
body = await request.body() # the raw bytes: the signature covers exactly these
try:
event = webhook.verify(body, dict(request.headers))
except Exception:
raise HTTPException(status_code=400)
handle(event) # event["type"], event["data"]; keep it quick, or queue it
return {"ok": True}
  • Prüfe den unveränderten Body: Wenn du das JSON einliest und neu serialisierst, ändern sich die Bytes – und damit die Signatur.
  • Lehne einen webhook-timestamp ab, der älter als fünf Minuten ist. So kann niemand einen abgefangenen Callback erneut senden. Die Bibliotheken übernehmen das für dich.
  • Wenn du das Secret wechselst (als Owner oder Admin auf der Seite für API-Schlüssel), erhältst du sofort ein neues. Das alte signiert noch 24 Stunden lang mit. Stelle deinen Server innerhalb dieser Zeit um.

Erst antworten, dann verarbeiten

Antworte innerhalb von 15 Sekunden mit einem beliebigen 2xx, dann gilt der Callback als zugestellt. Speichere zuerst das Ereignis und antworte; lade das Video oder die Bilder danach herunter. Ein Callback kann mehrfach eintreffen, etwa wenn deine Antwort unterwegs verloren geht. Verwende webhook-id, um ihn nur einmal zu verarbeiten.

Erneute Versuche

Bei jeder anderen Antwort (Fehlerstatus, Weiterleitung, keine Antwort innerhalb von 15 Sekunden oder nicht erreichbarer Server) versuchen wir es erneut – insgesamt sechsmal:

VersuchGesendet
1Sobald der Auftrag abgeschlossen ist
21 Minute später
35 Minuten danach
430 Minuten danach
52 Stunden danach
68 Stunden danach wird er als fehlgeschlagen markiert

Jeder Callback erscheint zusammen mit der zugehörigen Anfrage unter Anfragen im Dashboard: wohin er gesendet wurde, die Antwort auf jeden Versuch und wann der nächste Versuch ansteht. Mit Retry callback sendest du ihn dort sofort erneut – unabhängig davon, ob er bereits zugestellt wurde. Die enthaltenen Links werden bei jedem Versuch neu erstellt.

Callbacks gehen nur an öffentlich erreichbare https-URLs: nicht an localhost, private Adressen oder URLs, die auf eine solche Adresse verweisen. Weiterleitungen folgen sie nicht, verwende also die endgültige URL. Zum Testen während der Entwicklung kannst du einen Tunnel (ngrok, Cloudflare Tunnel) oder einen Request-Inspector nutzen.

Fragen oder fehlt etwas? Frag unser Support-Team und wir antworten per E-Mail, meist innerhalb einer Minute.