
Kling API Node.js Example: fetch, Poll, and Webhooks
Working Node.js/TypeScript code for Kling image-to-video: submit with fetch, poll the task with async/await, or verify a signed webhook in Express.
Most Kling API examples are curl snippets you have to translate yourself. This one is Node.js: plain fetch, no provider SDK, submitting an image-to-video job and getting the finished clip back two ways — a polling loop and an Express webhook handler.
Submit an image-to-video job
You need Node 18+ (for global fetch) and an API key. No kling-sdk, no auth dance — one POST with provider, model, method, and params:
const API_KEY = process.env.AIGENWAY_API_KEY!; // sk_live_...
const BASE_URL = "https://api.aigenway.com/v1";
async function createGeneration(provider: string, model: string, method: string, params: Record<string, unknown>) {
const res = await fetch(`${BASE_URL}/generations`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({ provider, model, method, params }),
});
if (!res.ok) throw new Error(`generation failed: ${res.status} ${await res.text()}`);
return res.json() as Promise<{ id: string; status: string }>;
}
const task = await createGeneration("kling", "kling-v2-master", "image_to_video", {
prompt: "slow dolly-in, hair moving gently in the wind, cinematic",
input_image: "https://example.com/character.png",
duration: 5,
});
console.log(task.id, task.status);
Kling ships several versions with different speed/quality trade-offs, and the exact codes change as the provider ships new ones — list what's live on your account before hardcoding a string:
curl "https://api.aigenway.com/v1/methods?provider=kling" \
-H "Authorization: Bearer sk_live_..."
Poll the task with async/await
createGeneration returns a task immediately — video rendering happens async. A small polling loop with setTimeout is enough for a script or a background job:
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function waitForResult(taskId: string, { timeoutMs = 120_000, intervalMs = 2_000 } = {}) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const res = await fetch(`${BASE_URL}/generations/${taskId}`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
if (!res.ok) throw new Error(`poll failed: ${res.status} ${await res.text()}`);
const data = await res.json();
if (data.status === "succeeded" || data.status === "failed") return data;
await sleep(intervalMs);
}
throw new Error(`task ${taskId} did not finish in time`);
}
const result = await waitForResult(task.id);
console.log(result.status, result.outputs);
This same loop works unchanged for Veo, Seedance, or any image model on the gateway — the task shape and status values are identical across providers, so you write the polling code once.
Handle the webhook instead
Polling is fine for a CLI tool, but a server should skip it. Set callback_url on the original request and the gateway POSTs the finished task to your endpoint the moment it's ready, signed so you can verify it came from Aigenway:
const task = await createGeneration("kling", "kling-v2-master", "image_to_video", {
prompt: "slow dolly-in, hair moving gently in the wind, cinematic",
input_image: "https://example.com/character.png",
duration: 5,
callback_url: "https://your-app.example.com/webhooks/aigenway",
});
An Express handler that verifies the signature before trusting the payload:
import express from "express";
import crypto from "node:crypto";
const app = express();
app.use(express.raw({ type: "application/json" }));
const WEBHOOK_SECRET = process.env.AIGENWAY_WEBHOOK_SECRET!;
app.post("/webhooks/aigenway", (req, res) => {
const signature = req.header("X-Aigenway-Signature") ?? "";
const expected = crypto.createHmac("sha256", WEBHOOK_SECRET).update(req.body).digest("hex");
if (signature !== expected) return res.status(401).send("bad signature");
const task = JSON.parse(req.body.toString("utf8"));
console.log(task.id, task.status, task.outputs);
res.status(200).send("ok");
});
app.listen(3000);
Check the exact header name and payload shape in the webhooks docs before wiring this into production — this is the pattern, not a guarantee of the literal header string.
Why fetch and a gateway beat a Kling SDK
- Zero dependencies. Node's built-in
fetchandcryptoare enough — nothing to pin, nothing that breaks on a Node upgrade. - One task contract, every model.
createGenerationandwaitForResultare unchanged if you swap in Veo, Seedance, or an image model — only theprovider/model/paramsvalues differ. - Prepaid USD billing. No Kling credit pack to forecast; pay per generation and read your balance from the API.
- Idempotent by default. The
Idempotency-Keyheader means a retried request on a flaky connection never submits (or bills) the same clip twice.