Plataforma

Callbacks (webhooks)

Em vez de fazer polling, inclua um callback_url na requisição de imagem ou vídeo. Quando a geração terminar ou falhar, a Zurelay envia o resultado via POST para essa URL, com uma assinatura para você confirmar que veio de nós.

Solicite um callback

Adicione callback_url a qualquer uma destas requisições. Deve ser uma URL https acessível na internet pública.

EndpointO que acontece
POST /v1/videosInicia o vídeo normalmente. Quando a geração termina, o callback traz o vídeo pronto (ou o motivo da falha). Não é preciso fazer polling.
POST /v1/images/generationsResponde imediatamente com 202 e o id da requisição, em vez de esperar pelas imagens, gera as imagens em segundo plano e depois envia o callback.
POST /v1/images/editsO mesmo vale para edições: um campo JSON ou um campo de formulário em uma requisição multipart.
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"
}'

Requisições de imagem com callback

Sem callback_url, a requisição de imagem espera e responde com as imagens, como faz a API da OpenAI. Com ele, a requisição é aceita imediatamente e a resposta traz a tarefa. Só há cobrança se as imagens forem geradas:

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

Você também pode fazer polling: GET /v1/images/{id} retorna o mesmo objeto, com links atualizados em data quando o status for completed. Os callbacks sempre trazem links, nunca base64, independentemente do valor de response_format.

O que enviamos

Um POST por evento, com um corpo JSON: type, timestamp e data, a tarefa exatamente como a API a retorna (o objeto de vídeo ou a requisição de imagem acima), com links de download válidos por 24 horas.

EventoQuando
video.completedO vídeo está pronto: data.url permite baixá-lo.
video.failedNão foi possível gerar o vídeo: data.error informa o motivo. Você não recebeu nenhuma cobrança.
image.completedAs imagens estão prontas: data.data contém um link para cada uma.
image.failedNenhuma imagem foi gerada: data.error informa o motivo. Você não recebeu nenhuma cobrança.
Um callback de vídeo
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"
}
}
Um callback de imagemmm (corpo)
{
"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"
}
}
CabeçalhoValor
webhook-idmsg_…: o mesmo em todas as novas tentativas deste callback. Use-o para ignorar um callback que você já processou.
webhook-timestampQuando esta tentativa foi enviada, em segundos Unix.
webhook-signaturev1, seguido da assinatura (veja abaixo). Duas assinaturas, separadas por espaço, durante um dia após você trocar o segredo.
user-agentZurelay-Webhooks/1.0

Verifique a assinatura

Os callbacks seguem o formato Standard Webhooks, o mesmo usado pelos webhooks da OpenAI. A assinatura é um HMAC-SHA256 de <webhook-id>.<webhook-timestamp>.<body>, calculado com o segredo de assinatura do seu workspace e codificado em base64. Encontre o segredo (que começa com whsec_) na página API Chaves de do painel e mantenha-o apenas no seu servidor.

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}
  • Verifique o corpo bruto: fazer o parsing e serializar o JSON novamente altera os bytes e, com eles, a assinatura.
  • Rejeite um webhook-timestamp com mais de cinco minutos: isso impede que alguém reenvie um callback interceptado. As bibliotecas fazem isso por você.
  • A troca do segredo (por proprietários e administradores, na página Chaves de API) gera um novo imediatamente. O antigo continua assinando junto com o novo por 24 horas, então atualize seu servidor dentro desse prazo.

Responda antes de processar

Responda com qualquer 2xx em até 15 segundos para que o callback seja considerado entregue. Salve o evento e responda primeiro; baixe o vídeo ou as imagens depois. Um callback pode chegar mais de uma vez (se a sua resposta se perder no caminho, por exemplo), então use webhook-id para processar cada um apenas uma vez.

Novas tentativas

Em qualquer outro caso (status de erro, redirecionamento, ausência de resposta em 15 segundos ou servidor inacessível), tentamos novamente, até um total de seis tentativas:

TentativaEnvio
1Assim que a tarefa termina
21 minuto depois
35 minutos após a anterior
430 minutos após a anterior
52 horas após a anterior
68 horas depois disso, então é marcado como falha

Cada callback aparece junto da sua requisição em Requisições no painel: para onde foi enviado, a resposta de cada tentativa e quando será a próxima. A opção Reenviar callback envia o callback de novo na hora, tenha ele sido entregue ou não. Os links incluídos são renovados a cada tentativa.

Callbacks são enviados apenas para URLs https na internet pública: nada de localhost, endereços privados ou domínios que resolvam para esses endereços. Eles não seguem redirecionamentos, então use a URL final. Para testá-los durante o desenvolvimento, use um túnel (ngrok, Cloudflare Tunnel) ou um inspetor de requisições.

Tem dúvidas ou falta algo? Fale com nossa equipe de suporte e responderemos por e-mail, geralmente em até um minuto.