Kling motion control with mode webhook: a Python verifier for rotation
Submit a Kling 3.0 motion control job with mode webhook and verify the sume-v1 signature in Python, including the two-entry header during a secret rotation.

A Kling 3.0 Motion Control job can take longer than the 30-second sync wait, so submit it with mode: "webhook" and a webhook_url and let Sume call you when it ends. The delivery carries job.completed, job.failed, or job.canceled, signed with HMAC SHA 256 over <timestamp>.<raw_body>. Verify it against every sume-v1= entry in the header, because during a secret rotation the header carries two.
Keep a polling fallback on GET /v1/jobs/:id/status. Webhook mode stores a callback for terminal delivery only.
Submit the job
The webhook URL has to be public HTTPS. Sume rejects localhost, private-network, and non-HTTPS URLs. The route body is strict, so send only the documented fields.
curl -X POST https://api.sume.com/v1/kling/3.0/motion-control \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: mc-hook-001" \
-d '{"image_url": "https://example.com/mascot.png", "motion_video_url": "https://example.com/dance.mp4", "duration_seconds": 12, "mode": "webhook", "webhook_url": "https://hooks.example.com/sume"}'What arrives
Delivery is terminal only. There are no progress events, so do not build a progress bar on the webhook.
| Event | status | Body carries | Your action |
|---|---|---|---|
| job.completed | OK | payload.artifacts with a media.sume.com URL | Copy the MP4 to your own storage |
| job.failed | ERROR | an error object | Fix the input, resubmit with a new key; the reservation is refunded |
| job.canceled | ERROR | an error object | Treat as ended, no output |
Verify the signature
The headers are x-sume-webhook-timestamp and x-sume-webhook-signature. Reject the delivery when the timestamp is more than five minutes from your clock. Compare each sume-v1= entry in constant time, and refuse to run at all if the secret is empty. Your secret is on the Webhooks tab of the dashboard, and the delivery worker reads it from SUME_COM_WEBHOOK_SIGNING_SECRET.
import hashlib
import hmac
import time
def verify(raw_body, timestamp, header, secret, tolerance=300):
if not secret:
raise ValueError('signing secret is empty')
try:
ts = int(timestamp)
except (TypeError, ValueError):
return False
if abs(int(time.time()) - ts) > tolerance:
return False
digest = hmac.new(secret.encode(), f'{ts}.'.encode() + raw_body, hashlib.sha256).hexdigest()
expected = f'sume-v1={digest}'
ok = False
for entry in header.split(','):
ok |= hmac.compare_digest(entry.strip(), expected)
return ok
body = b'{"event":"job.completed"}'
ts = str(int(time.time()))
sig = 'sume-v1=' + hmac.new(b'test-secret', ts.encode() + b'.' + body, hashlib.sha256).hexdigest()
print(verify(body, ts, sig, 'test-secret')) # TrueHandle it safely
Run webhooks for Actions, Formats, and Agent Completions share the same secret and signature scheme, so one verifier covers all of them.
- Verify against the raw bytes, not a re-serialized JSON object.
- Answer 2xx fast and do the copy of the MP4 in the background.
- Make the handler idempotent on
job_id; this keeps a repeated delivery harmless. - Keep polling
GET /v1/jobs/:id/statusfor any job that has not reported after your own deadline.
Sources
Related posts
More in Developers
- Let browsers start Sume jobs through your server, not with your key
Browsers must never hold a Sume API key. A server route authenticates the user, checks the input, derives an Idempotency-Key and returns only the status URL.
- List every Format run: no GET /v1/format-runs, page per Format
GET /v1/format-runs does not exist. List runs per Format with limit, next_cursor and has_more, or keep your own index of the data.id you stored at create.
- List Sume image models that take references or masks with jq
New image models land weekly. One curl and one jq filter on GET /v1/images/models show which ids accept input_references, mask_url or background, and how many.
- How do I add a listen-to-this-page audio version with TTS?
Turn each article into an audio file with one async TTS job per page: a 9,000-character article costs 43 cents on Sume. What it does not replace.
Written by Sume