Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Configure webhook callbacks for Kling AI task completion. Use when building event-driven pipelines or replacing polling. Trigger with phrases like 'klingai webhook', 'kling ai callback', 'klingai notifications', 'video completion webhook'.
.claude/skills/jeremylongshore-klingai-webhook-config/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 24% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 163% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 17% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 33% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 131% | 0% |
Instead of polling task status, pass a callback_url when creating a task. Kling AI will POST the completed task result to your URL when generation finishes. This eliminates polling overhead and reduces API calls.
Supported on: All video generation endpoints (text2video, image2video, video-extend, lip-sync, effects)
callback_url in your task creation requestsucceed or failed), Kling POSTs the full result to your URLpythonimport jwt, time, os, requests BASE = "https://api.klingai.com/v1" def get_headers(): ak, sk = os.environ["KLING_ACCESS_KEY"], os.environ["KLING_SECRET_KEY"] token = jwt.encode( {"iss": ak, "exp": int(time.time()) + 1800, "nbf": int(time.time()) - 5}, sk, algorithm="HS256", headers={"alg": "HS256", "typ": "JWT"} ) return {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} # Create task with callback response = requests.post(f"{BASE}/videos/text2video", headers=get_headers(), json={ "model_name": "kling-v2-master", "prompt": "A futuristic city skyline at night with neon lights", "duration": "5", "mode": "standard", "callback_url": "https://your-app.com/webhooks/kling", # your endpoint }) task_id = response.json()["data"]["task_id"] print(f"Task {task_id} submitted with callback -- no polling needed")
pythonfrom flask import Flask, request, jsonify import hmac import hashlib import json app = Flask(__name__) @app.route("/webhooks/kling", methods=["POST"]) def kling_webhook(): payload = request.get_json() task_id = payload["data"]["task_id"] status = payload["data"]["task_status"] if status == "succeed": video_url = payload["data"]["task_result"]["videos"][0]["url"] print(f"Task {task_id} complete: {video_url}") # Download video, store to S3, notify user, etc. process_completed_video(task_id, video_url) elif status == "failed": error = payload["data"].get("task_status_msg", "Unknown error") print(f"Task {task_id} failed: {error}") handle_failure(task_id, error) return jsonify({"received": True}), 200
javascriptimport express from "express"; const app = express(); app.use(express.json()); app.post("/webhooks/kling", (req, res) => { const { data } = req.body; const { task_id, task_status } = data; if (task_status === "succeed") { const videoUrl = data.task_result.videos[0].url; console.log(`Task ${task_id} complete: ${videoUrl}`); processVideo(task_id, videoUrl); } else if (task_status === "failed") { console.error(`Task ${task_id} failed: ${data.task_status_msg}`); } res.json({ received: true }); }); app.listen(3000);
json{ "code": 0, "message": "success", "data": { "task_id": "abc123...", "task_status": "succeed", "task_status_msg": "", "task_result": { "videos": [{ "id": "vid_001", "url": "https://cdn.klingai.com/...", "duration": "5.0" }] } } }
pythonimport time from collections import defaultdict class WebhookManager: """Track webhook delivery and fall back to polling on failure.""" def __init__(self, poll_fallback_sec: int = 300): self.pending = {} # task_id -> submission_time self.poll_fallback_sec = poll_fallback_sec def register(self, task_id: str): self.pending[task_id] = time.time() def mark_received(self, task_id: str): self.pending.pop(task_id, None) def get_stale_tasks(self) -> list: """Tasks that haven't received a callback within threshold.""" now = time.time() return [tid for tid, submitted in self.pending.items() if now - submitted > self.poll_fallback_sec] def fallback_poll(self, client): """Poll stale tasks that missed their callback.""" for task_id in self.get_stale_tasks(): try: result = client._get(f"/videos/text2video/{task_id}") status = result["data"]["task_status"] if status in ("succeed", "failed"): self.mark_received(task_id) return result except Exception: pass
| Requirement | Detail | |------------|--------| | Protocol | HTTPS only | | Response | Return 2xx within 5 seconds | | Availability | Must be publicly reachable | | Idempotency | Handle duplicate deliveries gracefully | | Timeout | Kling retries on timeout, so process async |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 29,262 | 14,419 | -51% | 1 | 1 | 0% | 2,724 | 3,370 | +24% | 0 | 0 | — |
case-02 | pass→pass | 27,601 | 25,853 | -6% | 1 | 1 | 0% | 4,928 | 6,223 | +26% | 0 | 0 | — |
case-03 | pass→pass | 13,620 | 8,587 | -37% | 1 | 1 | 0% | 2,433 | 3,282 | +35% | 0 | 0 | — |
case-04 | fail→pass | 20,864 | 2,721 | -87% | 1 | 1 | 0% | 729 | 1,915 | +163% | 0 | 0 | — |
case-05 | pass→pass | 16,720 | 13,683 | -18% | 1 | 1 | 0% | 2,318 | 3,282 | +42% | 0 | 0 | — |
case-22 | pass→pass | 15,270 | 20,298 | +33% | 1 | 1 | 0% | 3,080 | 4,639 | +51% | 0 | 0 | — |
case-06 | pass→pass | 15,859 | 16,024 | +1% | 1 | 1 | 0% | 2,024 | 2,672 | +32% | 0 | 0 | — |
case-07 | fail→pass | 26,377 | 3,351 | -87% | 1 | 1 | 0% | 1,765 | 2,069 | +17% | 0 | 0 | — |
case-08 | pass→pass | 14,876 | 10,468 | -30% | 1 | 1 | 0% | 1,693 | 2,442 | +44% | 0 | 0 | — |
case-09 | pass→pass | 21,173 | 21,880 | +3% | 1 | 1 | 0% | 2,712 | 4,547 | +68% | 0 | 0 | — |
case-10 | pass→pass | 15,521 | 11,655 | -25% | 1 | 1 | 0% | 1,764 | 3,545 | +101% | 0 | 0 | — |
case-11 | fail→pass | 14,571 | 4,124 | -72% | 1 | 1 | 0% | 1,772 | 2,353 | +33% | 0 | 0 | — |
case-12 | pass→pass | 15,408 | 7,127 | -54% | 1 | 1 | 0% | 2,042 | 2,567 | +26% | 0 | 0 | — |
case-13 | pass→pass | 15,803 | 8,212 | -48% | 1 | 1 | 0% | 1,651 | 2,068 | +25% | 0 | 0 | — |
case-14 | pass→pass | 9,571 | 3,338 | -65% | 1 | 1 | 0% | 1,760 | 2,078 | +18% | 0 | 0 | — |
case-15 | pass→pass | 13,847 | 9,209 | -33% | 1 | 1 | 0% | 1,537 | 2,030 | +32% | 0 | 0 | — |
case-16 | fail→pass | 23,293 | 10,092 | -57% | 1 | 1 | 0% | 1,072 | 2,472 | +131% | 0 | 0 | — |
case-17 | pass→pass | 17,678 | 13,164 | -26% | 1 | 1 | 0% | 2,079 | 3,153 | +52% | 0 | 0 | — |
case-23 | pass→pass | 18,082 | 17,099 | -5% | 1 | 1 | 0% | 2,169 | 3,810 | +76% | 0 | 0 | — |
case-18 | pass→pass | 10,229 | 7,234 | -29% | 1 | 1 | 0% | 919 | 1,881 | +105% | 0 | 0 | — |
case-19 | pass→pass | 16,064 | 7,824 | -51% | 1 | 1 | 0% | 3,058 | 2,908 | -5% | 0 | 0 | — |
case-20 | pass→pass | 9,249 | 5,758 | -38% | 1 | 1 | 0% | 1,777 | 2,640 | +49% | 0 | 0 | — |
case-21 | pass→pass | 14,927 | 20,111 | +35% | 1 | 1 | 0% | 3,076 | 4,647 | +51% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 21 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +22 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.