AI video API webhook vs polling: which one to use and when
A render takes minutes, so something has to bridge the gap between “submitted” and “here is your file”. There are four mechanisms, and they are not interchangeable — each survives a different failure.
Long poll
curl "https://genace.ai/api/v1/jobs/$JOB_ID?wait=30" \
-H "Authorization: Bearer $GENACE_API_KEY"
Blocks server-side for up to wait seconds and returns whatever state it
reached. wait=0 returns immediately.
Use it when the caller is a script or a CLI that can just sit there.
Fails when your runtime has a hard request timeout shorter than the render. Serverless functions are the usual culprit.
SSE
curl -N "https://genace.ai/api/v1/jobs/$JOB_ID/stream" \
-H "Authorization: Bearer $GENACE_API_KEY"
Holds a connection open and pushes an event each time the state moves. The stream polls upstream on your behalf.
Use it when a human is watching — it is what a progress bar wants.
Fails when nobody is watching. A dropped connection means the events go nowhere, and mobile networks drop connections.
Webhook
You supply a URL; we POST to it when the job reaches a terminal state.
Use it when the work is server-side and there is somewhere durable to receive the result. Nothing has to stay connected, which is the whole point.
Fails when your endpoint is down at the moment we call it, or is not publicly reachable. Local development is the common trap here.
The cron backstop
Jobs that nobody is watching still get advanced on a schedule. This is not something you call — it is what makes the other three safe to abandon.
Why it matters for agents specifically: the session ends, the user closes
the tab, the process exits. Without a backstop, that job is stuck in running
forever, holding credits it will never release. With one, it reaches a
terminal state and — if it failed — refunds.
Picking
| Your caller is | Use |
|---|---|
| A script or CLI | long poll |
| A UI with a progress bar | SSE |
| A server-side pipeline | webhook |
| An agent tool call | return the job id, collect elsewhere |
That last row is the one people get wrong. A tool that blocks for four minutes is dead weight in the agent’s transcript, and the render outlives the session anyway.
All four have the same failure semantics
Whichever you use, a terminal state of failed is a successful HTTP
response describing a failure. It will not throw. Check state, not the
status code — see
job state failed and UPSTREAM_FAIL.
Failed and canceled jobs refund their credits in full, regardless of how you found out.
Models covered on this page
Where these facts come from
- codebase: src/ai/jobs/http.ts — pollUntilTerminal and createJobSseStream
- codebase: src/app/api/v1/jobs/[id]/stream/route.ts — SSE endpoint
- codebase: src/app/api/cron/poll/route.ts — the scheduled backstop