Plataforma

Callbacks (webhooks)

Em vez de consultar o estado periodicamente, inclua um callback_url no pedido de imagem ou vídeo. Quando estiver pronto, ou se falhar, a Zurelay envia o resultado para esse URL por POST, com uma assinatura que permite confirmar que veio de nós.

Pedir um callback

Adicione callback_url a qualquer um destes pedidos. Tem de ser um URL https acessível na Internet pública.

EndpointO que acontece
POST /v1/videosInicia a geração do vídeo como habitualmente. Quando termina, o callback inclui o vídeo concluído (ou o motivo da falha). Não é preciso consultar o estado periodicamente.
POST /v1/images/generationsResponde de imediato com 202 e o id do pedido, em vez de esperar pelas imagens, gera-as em segundo plano e depois envia um callback.
POST /v1/images/editsO mesmo, para edições: um campo JSON ou um campo de formulário num pedido 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"
}'

Pedidos de imagem com callback

Sem callback_url, um pedido de imagem espera e responde com as imagens, tal como a API da OpenAI. Com este campo, o pedido é aceite de imediato e a resposta contém a tarefa. Só há cobrança se forem geradas imagens:

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}

Também pode consultar o estado periodicamente: GET /v1/images/{id} devolve o mesmo objeto, com novas ligações em data assim que o seu status for completed. Os callbacks incluem sempre ligações, 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 devolve (o objeto de vídeo ou o pedido de imagem acima), com ligações de transferência válidas durante 24 horas.

EventoQuando
video.completedO vídeo está pronto: data.url permite descarregá-lo.
video.failedNão foi possível gerá-lo: data.error indica o motivo. Não foi cobrado qualquer valor.
image.completedAs imagens estão prontas: data.data contém uma ligação para cada uma.
image.failedNão foi gerada nenhuma imagem: data.error indica o motivo. Não foi cobrado qualquer valor.
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_…: mantém-se em todas as novas tentativas deste callback. Use-o para ignorar um callback que já tenha processado.
webhook-timestampQuando esta tentativa foi enviada, em segundos Unix.
webhook-signaturev1, seguido da assinatura (ver abaixo). Durante um dia após a rotação do segredo, são enviadas duas assinaturas, separadas por um espaço.
user-agentZurelay-Webhooks/1.0

Verificar a assinatura

Os callbacks seguem o formato Standard Webhooks, o mesmo que os webhooks da OpenAI usam. A assinatura é um HMAC-SHA256 de <webhook-id>.<webhook-timestamp>.<body>, em base64, calculado com o segredo de assinatura do seu espaço de trabalho como chave. Encontre o segredo (começa por whsec_) na página API Chaves de do painel e guarde-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 em bruto: interpretar e voltar a serializar o JSON altera os bytes e, com eles, a assinatura.
  • Rejeite um webhook-timestamp com mais de cinco minutos: isto impede a reutilização de um callback intercetado. As bibliotecas fazem esta verificação por si.
  • A rotação do segredo (disponível para proprietários e administradores na página Chaves de API) cria um novo de imediato. O antigo continua a ser usado para assinar em paralelo durante 24 horas, por isso configure o seu servidor para usar o novo dentro desse prazo.

Responder primeiro, processar depois

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

Novas tentativas

Qualquer outro resultado (um estado de erro, um redirecionamento, ausência de resposta no prazo de 15 segundos ou um servidor inacessível) leva a uma nova tentativa, até um total de seis:

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, passa a estar marcado como falhado

Cada callback aparece junto do respetivo pedido em Pedidos no painel: o destino, a resposta de cada tentativa e a data da próxima. A opção Repetir callback envia-o de novo imediatamente, tenha sido entregue ou não. Os links incluídos são renovados a cada tentativa.

Os callbacks só são enviados para URLs https na internet pública: não para localhost, endereços privados ou domínios que resolvam para um desses endereços. Não seguem redirecionamentos, por isso use o URL final. Para os testar durante o desenvolvimento, use um túnel (ngrok, Cloudflare Tunnel) ou um inspetor de pedidos.

Tem dúvidas ou falta alguma coisa? Contacte a nossa equipa de apoio e responderemos por email, normalmente num minuto.