Skip to main content

Overview

Webhooks send HTTP POST notifications when task status changes, eliminating polling.
Webhooks are optional. You can always use polling instead.

Setup

Include callBackUrl when creating a task:
const response = await fetch('https://kinovi.ai/api/v1/jobs/createTask', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    model: 'seedance-20',
    callBackUrl: 'https://yourapp.com/api/webhook/kinovi',
    inputs: {
      prompt: 'A beautiful landscape',
      duration: '5'
    }
  })
});

Webhook Payload

All webhook payloads use the following envelope format:
{
  "code": 200,
  "message": "success",
  "data": { ... }
}

Success

{
  "code": 200,
  "message": "success",
  "data": {
    "taskId": "task_clxxxxxx",
    "orderId": "order_clxxxxxx",
    "model": "seedance-20",
    "state": "success",
    "creditsUsed": 200,
    "output": [
      {
        "url": "https://static.kinovi.ai/generated/video.mp4",
        "width": 1280,
        "height": 720
      }
    ],
    "error": null,
    "createTime": 1735599634000,
    "completeTime": 1735599754000,
    "updateTime": 1735599754000
  }
}

Failure

{
  "code": 200,
  "message": "success",
  "data": {
    "taskId": "task_clxxxxxx",
    "orderId": "order_clxxxxxx",
    "model": "seedance-20",
    "state": "fail",
    "creditsUsed": 0,
    "output": null,
    "error": {
      "code": "2038",
      "message": "Your input text violates platform rules. Please modify and try again."
    },
    "createTime": 1735599634000,
    "completeTime": null,
    "updateTime": 1735599754000
  }
}

Payload Fields

FieldTypeDescription
data.taskIdstringUnique task identifier
data.orderIdstringOrder identifier
data.modelstringModel used for generation (e.g. seedance-20)
data.statestring"success" or "fail"
data.creditsUsednumberCredits consumed for this task
data.outputarray | nullArray of output objects on success, null on failure
data.output[].urlstringURL of the generated video
data.output[].widthnumberVideo width in pixels
data.output[].heightnumberVideo height in pixels
data.errorobject | nullError details on failure, null on success
data.error.codestringError code (e.g. "2038", "4011", "500" for generic errors). See Task Error Messages.
data.error.messagestringHuman-readable error message. See Task Error Messages.
data.createTimenumberTask creation timestamp (ms)
data.completeTimenumber | nullTask completion timestamp (ms), null if failed before completion
data.updateTimenumberLast update timestamp (ms)
The webhook payload format matches the Get Task Status response — same fields, same structure. You can use the same parsing logic for both.

Webhook Handler Examples

app.post('/api/webhook/kinovi', async (req, res) => {
  const { data } = req.body;

  if (data.state === 'success') {
    const videoUrl = data.output[0].url;
    await saveVideo(data.taskId, videoUrl);
  } else if (data.state === 'fail') {
    console.error('Failed:', data.taskId, data.error?.message);
  }

  res.status(200).json({ message: 'ok' });
});

Best Practices

  • Always respond 200 immediately — process asynchronously if needed
  • Implement idempotency — use taskId to deduplicate (webhooks may be delivered multiple times)
  • Use HTTPS for webhook endpoints
  • Implement fallback polling for critical tasks — webhooks are best-effort

Retry Policy

Webhook delivery is best-effort. If the request fails (non-2xx response or network error), it will not be retried. Use polling as a fallback for critical tasks.

Testing Locally

Use ngrok to expose your local server:
ngrok http 3000
# Then use the ngrok URL as callBackUrl