Kling motion control webhook_url: a Python receiver that verifies
Send mode async plus webhook_url on a Kling motion control submit, then verify x-sume-webhook-signature (HMAC SHA-256 over timestamp.body) in Python.

Kling motion control on Sume takes mode: "async" with a webhook_url, and Sume posts the terminal job to that URL with an x-sume-webhook-signature header. Verify it with HMAC SHA-256 over <timestamp>.<raw_body>, and refuse the call when the secret is empty.
Two names for the same idea
This is easy to mix up. The motion control body uses webhook_url. The OpenRouter-shaped POST /v1/videos body uses callback_url, and it must be HTTPS. Both give you the same signed job envelope on completion, not a video.generation.* event. If you move a Kling job between the two surfaces, rename the field.
The submit
Add the field next to the usual motion body: one visual source, the motion_video_url, and duration_seconds (1 to 30). Keep polling as a backup; Sume's docs say to keep polling even when you use a webhook.
The receiver
The signature header looks like sume-v1=<hex>. During a secret rotation it holds one entry per live secret, newest first, separated by commas, so accept the delivery when any entry matches. Reject timestamps that are older than your replay window; five minutes is the documented default.
import hashlib, hmac, os, time
def verify(raw: bytes, ts: str, header: str, secret: str) -> bool:
if not secret:
raise RuntimeError("signing secret is empty")
if abs(time.time() - int(ts)) > 300:
return False
mac = hmac.new(secret.encode(), ts.encode() + b"." + raw,
hashlib.sha256).hexdigest()
for part in header.split(","):
k, _, v = part.strip().partition("=")
if k == "sume-v1" and hmac.compare_digest(v, mac):
return True
return False
SECRET = os.environ.get("SUME_COM_WEBHOOK_SIGNING_SECRET", "")What to check on each delivery
| Item | Value |
|---|---|
| Signed string | <timestamp>.<raw_body> |
| Algorithm | HMAC SHA-256, hex |
| Headers | x-sume-webhook-timestamp, x-sume-webhook-signature |
| Rotation | one sume-v1= entry per live secret |
| Failed or canceled job | status ERROR with an error object |
Next step
Call verify on the raw bytes, not on parsed JSON that you re-serialised, because the signature covers the exact body. Read the secret from the dashboard Webhooks tab or GET /v1/webhooks/signing-secret, then fetch the video from GET /v1/jobs/:id/result.
Common failures
The most common failure is a handler that parses the JSON, then dumps it again and verifies that string. Any change in spacing or key order breaks the HMAC. Read the raw request body once, verify it, and only then parse it.
The second is a clock that drifts. The five-minute window is on the timestamp header, so a server that is ten minutes slow will reject every delivery. Sync the clock before you blame the secret.
The third is a missing secret in a new environment. The code above raises when the secret is empty, because an empty key would make the signature trivial to forge.
Where the receiver fits
Keep the handler thin: verify, write the job id and status to a table, return 200 fast, and do the download in a worker. A job that finishes while your server is down is not lost, because the job stays readable at GET /v1/jobs/:id/status and GET /v1/jobs/:id/result. A small sweeper that polls jobs older than a few minutes with no terminal row closes the gap.
For a 30-second motion clip that is a good fit. The render takes long enough that a poll loop wastes requests, and the delivery tells you the moment it is ready.
Sources
Related posts
More in Developers
- Compare 4 image models in one labelled 2x2 sheet with cost per tile
Send one prompt to four Sume image models, paste the results into a 2x2 Pillow sheet, and print each model id with its billed usage.cost on its own tile.
- Last frame of an Omni clip: fixing frame_time_out_of_range
Asking Sume video-frames for t equal to the clip length fails with frame_time_out_of_range. Take the last frame at duration minus a frame, in Python.
- Video-router image_url to /v1/videos frame_images, in Python
Map image_url, end_image_url and reference_image_urls from /v1/video-router/generate to frame_images and input_references on /v1/videos, with Python.
- LinkedIn API sunsets: 202510, 202511 and 202601 dates, and the upgrade
LinkedIn lists 202510 sunsetting October 15, 2026, 202511 on November 16, 2026 and 202601 on January 15, 2027. Pick one target and test with a Sume clip.
Written by Sume