Get started
Webhooks & Polling
OyeFilmy AI generation is asynchronous. Collect your results by webhook, or by polling.
How async processing works
When you call POST /api/generate, the server immediately returns a predictionId and begins processing in the background. Video typically takes 30–120 seconds; images are much quicker.
A background worker settles every job whether or not anyone is watching, so you can submit work and disconnect. Pick whichever collection method suits you:
Receive a callback
Pass a webhookUrl when you start the generation. We POST to it once the job reaches a terminal state.
const res = await fetch("https://api.oyefilmy.com/api/generate", {
method: "POST",
headers: {
"Authorization": "Bearer of_live_YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
modelKey: "seedance_2_0_fast_t2v",
prompt: "A drone shot flying over misty mountains at sunrise",
aspectRatio: "16:9",
duration: 5,
webhookUrl: "https://your-app.example.com/hooks/oyefilmy",
}),
});
const { predictionId } = await res.json();
// Nothing more to do — the result arrives at your endpoint.Requirements. webhookUrl must be https and a public host, and it only works on an API-key request — a dashboard session has a browser polling for it. An invalid URL is rejected at submit time rather than silently dropped.
Payload
Sent as application/json. The version field lets the shape grow without breaking your receiver.
{
"version": 1,
"event": "generation.completed", // or "generation.failed"
"jobId": "665f1c...",
"predictionId": "atlas-video-___abc123",
"status": "completed",
"modelKey": "seedance_2_0_fast_t2v",
"credits": 42, // what you were actually charged
"assetId": "665f20...",
"url": "https://cdn.oyefilmy.com/...mp4",
"type": "Video",
"error": null, // set on generation.failed
"createdAt": "2026-09-03T09:14:02.001Z",
"deliveredAt": "2026-09-03T09:15:11.442Z"
}Verifying the signature
Every delivery carries an OyeFilmy-Signature header. Your webhook secret is shown once, when you create the API key, on the API Keys page.
OyeFilmy-Signature: t=1772534111,v1=5f8b...64-hexCompute HMAC-SHA256 over the string `${t}.${rawBody}` using your webhook secret, and compare with v1 in constant time. Use the raw body — a re-serialised JSON object will not match.
import crypto from "node:crypto";
app.post("/hooks/oyefilmy",
express.raw({ type: "application/json" }), // raw bytes, not a parsed object
(req, res) => {
const header = req.get("OyeFilmy-Signature") ?? "";
const parts = new Map(header.split(",").map(p => {
const i = p.indexOf("=");
return [p.slice(0, i), p.slice(i + 1)];
}));
const t = Number(parts.get("t"));
const v1 = parts.get("v1") ?? "";
// Reject anything older than five minutes, so a captured delivery
// cannot be replayed later.
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > 300) {
return res.sendStatus(400);
}
const expected = crypto
.createHmac("sha256", process.env.OYEFILMY_WEBHOOK_SECRET)
.update(`${t}.${req.body}`)
.digest("hex");
const a = Buffer.from(v1);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body.toString("utf8"));
// ... do your work, then acknowledge
res.sendStatus(200);
});Delivery behaviour
| Your response | What we do |
|---|---|
2xx | Delivered. Never sent again. |
5xx, 429, timeout, connection error | Retried with exponential backoff, up to 5 attempts. |
Any other 4xx | Treated as permanent — the URL is wrong, so we stop rather than spend four more attempts on it. |
Answer within 10 seconds; acknowledge first and do your work afterwards. We do not follow redirects. If delivery is exhausted the result is unaffected — it stays on your account and remains available via GET /api/generate/status/:predictionId and GET /api/generate/history. A callback is a convenience on top of a stored result, never the only copy of it.
Which should I use?
Use webhooksfor anything that runs without a person watching — a server, a queue worker, a scheduled job, an agent that submits a batch and moves on. Use polling when you are already waiting synchronously and a callback endpoint would be more trouble than it is worth: a script, a notebook, a local experiment.
There is no need to choose exclusively. A webhookUrl does not disable polling, and the two are consistent because both read the same settled job.

