Platform

Callbacks (webhooks)

Geef je afbeeldings- of videoverzoek een callback_url mee in plaats van te pollen. Zodra het klaar is of mislukt, stuurt Zurelay het resultaat via POST naar die URL, met een handtekening zodat je weet dat het van ons komt.

Een callback aanvragen

Voeg callback_url toe aan een van deze verzoeken. Dit moet een https-URL zijn die via het openbare internet bereikbaar is.

EndpointWat er gebeurt
POST /v1/videosStart de video zoals gebruikelijk. Zodra de taak is afgerond, levert de callback de voltooide video (of de reden waarom het mislukte). Pollen is niet nodig.
POST /v1/images/generationsAntwoordt direct met 202 en de id van het verzoek in plaats van op de afbeeldingen te wachten, maakt ze op de achtergrond en stuurt daarna een callback.
POST /v1/images/editsHetzelfde, voor bewerkingen: een JSON-veld of een formulierveld in een multipart-verzoek.
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"
}'

Afbeeldingsverzoeken met een callback

Zonder callback_url wacht een afbeeldingsverzoek tot de afbeeldingen klaar zijn en stuurt ze terug, net als de API van OpenAI. Met dit veld wordt het verzoek direct geaccepteerd en krijg je de taak terug. Je betaalt alleen als er afbeeldingen zijn gemaakt:

Antwoord
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}

Je kunt ook pollen: GET /v1/images/{id} geeft hetzelfde object terug, met nieuwe links in data zodra de status gelijk is aan completed. Callbacks bevatten altijd links, nooit base64, ongeacht de waarde van response_format.

Wat we versturen

Eén POST per gebeurtenis, met een JSON-body: type, timestamp en data, de taak precies zoals de API die teruggeeft (het video-object of het afbeeldingsverzoek hierboven), met downloadlinks die 24 uur geldig zijn.

GebeurtenisWanneer
video.completedDe video is klaar: je downloadt hem via data.url.
video.failedDe video kon niet worden gemaakt: data.error geeft de reden. Er is niets in rekening gebracht.
image.completedDe afbeeldingen zijn klaar: data.data bevat een link naar elke afbeelding.
image.failedEr is geen afbeelding gemaakt: data.error geeft de reden. Er is niets in rekening gebracht.
Een videocallback
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"
}
}
Een afbeeldingscallback (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"
}
}
HeaderWaarde
webhook-idmsg_…: blijft hetzelfde bij elke nieuwe poging voor deze callback. Gebruik dit om callbacks over te slaan die je al hebt verwerkt.
webhook-timestampWanneer deze poging is verstuurd, in Unix-seconden.
webhook-signaturev1, gevolgd door de handtekening (zie hieronder). Na het roteren van je geheim zijn het er een dag lang twee, gescheiden door een spatie.
user-agentZurelay-Webhooks/1.0

De handtekening verifiëren

Callbacks volgen het Standard Webhooks-formaat, hetzelfde formaat dat OpenAI gebruikt voor webhooks. De handtekening is een HMAC-SHA256 van <webhook-id>.<webhook-timestamp>.<body>, met het ondertekeningsgeheim van je workspace als sleutel, in base64. Je vindt het geheim (het begint met whsec_) op de pagina API-sleutels in het dashboard. Bewaar het alleen op je server.

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}
  • Controleer de onbewerkte body: als je de JSON parset en opnieuw serialiseert, veranderen de bytes en daarmee de handtekening.
  • Weiger een webhook-timestamp die meer dan vijf minuten oud is: zo voorkom je dat iemand een onderschepte callback opnieuw verstuurt. De libraries doen dit voor je.
  • Als je het geheim roteert (als eigenaar of beheerder, op de pagina API-sleutels), krijg je direct een nieuw geheim. Het oude blijft daarnaast nog 24 uur ondertekenen, dus schakel je server binnen een dag over.

Eerst antwoorden, dan verwerken

Antwoord binnen 15 seconden met een 2xx en de callback geldt als afgeleverd. Sla de gebeurtenis op en antwoord eerst; download de video of afbeeldingen daarna. Een callback kan meerdere keren binnenkomen (bijvoorbeeld als je antwoord onderweg verloren ging), dus gebruik webhook-id om elke callback maar één keer te verwerken.

Nieuwe pogingen

In alle andere gevallen (een foutstatus, een redirect, geen antwoord binnen 15 seconden van een onbereikbare server) proberen we het opnieuw, tot in totaal zes pogingen:

PogingVerstuurd
1Zodra de taak is afgerond
21 minuut later
35 minuten daarna
430 minuten daarna
52 uur daarna
68 uur daarna, waarna de callback als mislukt wordt gemarkeerd

Elke callback staat bij het bijbehorende verzoek onder Verzoeken in het dashboard: waar hij naartoe ging, het antwoord op elke poging en wanneer de volgende poging is. Met Callback opnieuw proberen verstuur je hem meteen opnieuw, ook als hij al is afgeleverd. De links erin worden bij elke poging vernieuwd.

Callbacks gaan alleen naar https-URL's op het openbare internet: niet naar localhost, privéadressen van adressen die daarnaar verwijzen. Ze volgen geen redirects, dus gebruik de uiteindelijke URL. Om ze tijdens het ontwikkelen te testen, gebruik je een tunnel (ngrok, Cloudflare Tunnel) of een tool om verzoeken te inspecteren.

Vragen, of ontbreekt er iets? Vraag het ons supportteam en je krijgt antwoord per e-mail, meestal binnen een minuut.