Face swap API webhook: get job.completed instead of polling
Sume's face swap (Beta) takes mode webhook with a public HTTPS webhook_url, then sends one signed terminal event: job.completed, job.failed or job.canceled.

To get a callback when a face swap finishes, submit the run with mode: "webhook" and a public HTTPS webhook_url. Sume then sends one signed terminal event to that URL: job.completed, job.failed or job.canceled. You do not poll for completion, although the docs tell you to keep a polling fallback.
The face swap endpoint is POST /v1/models/sume/avatar-face-swap/v1.0/runs, a Beta model run documented on Face swap (Beta). It needs avatar_handle, video_url and quality. Of the four communication modes, only webhook is the right fit for a clip that routinely outlasts a 30-second HTTP wait.
How do I submit a face swap with a webhook?
Send the usual required fields and add the mode and URL. The source video must be a fetchable public HTTPS URL; signed or private URLs and provider task URLs are rejected, and the Beta worker targets sources of about 4-15 seconds with usable audio.
Webhook URLs follow the same rule as media inputs: localhost, private-network and non-HTTPS addresses are rejected at submit time, so a tunnel URL must be HTTPS and reachable from the internet.
curl -X POST https://api.sume.com/v1/models/sume/avatar-face-swap/v1.0/runs \
-H "Authorization: Bearer $SUME_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: faceswap-clip-042" \
-d '{
"avatar_handle": "product_host",
"video_url": "https://example.com/source-clip.mp4",
"quality": "plus",
"mode": "webhook",
"webhook_url": "https://example.com/hooks/sume"
}'What events does Sume send?
Sume sends terminal job events only. There are no progress or partial deliveries, so a webhook cannot drive a progress bar.
The same three events cover every generation job, face swap included. Face swap is a model run, so it falls on the generation-jobs side of Sume's two webhook surfaces, not the Action, Format or Agent run side.
| Event | When it is sent | Payload status |
|---|---|---|
job.completed | The job completed and a public result is available | OK |
job.failed | The job failed with a public error | ERROR, with an error object |
job.canceled | The job reached canceled state | ERROR, with an error object |
What do I do when the event arrives?
The webhook carries the job_id and, on success, the public artifacts. For a face swap, the finished resource exposes a public-safe video_url under media.sume.com when it is ready, and the face swap page says to prefer resource_status for readiness and job_status for polling. Fetch GET /v1/jobs/{job_id}/result if you want the full result rather than trusting only the callback body.
If a swap succeeds but you see no video_url, read the resource before retrying: a completed face swap job with an empty video_url is usually a readiness read, not a new paid run.
Is a webhook enough on its own?
No. Sume's guidance is to keep the polling fallback in place alongside the webhook, because a delivery can fail on your side. Two rules keep it safe.
First, verify the signature. Sume signs the raw JSON body with HMAC SHA 256 over <timestamp>.<raw_body> and sends x-sume-webhook-timestamp and x-sume-webhook-signature: sume-v1=<hex>. During a secret rotation the header carries one entry per live secret, so accept the delivery when any entry matches. Second, never answer a missed event by submitting a second paid job for the same intent: poll status_url for the job you already have, and reuse the same Idempotency-Key for any retry of the submit itself.
What should my receiver do?
Answer fast with a 2xx and do the work afterward. Reject a timestamp outside your replay window (the docs suggest five minutes as a default), then compare the signature against every sume-v1= entry. Your signing secret is on the Webhooks tab of the dashboard, or from GET /v1/webhooks/signing-secret with an API key that carries account:read.
Store the job_id you got at submit time next to your own record, so a late or repeated event can be matched and ignored if you already handled it. Because you reuse the same Idempotency-Key for retries of the submit, a duplicate submit does not create a second face swap, and your receiver should be just as safe against a duplicate event.
Sources
Related posts
More in Sume Avatar 1.0
- Face swap for UGC ads: source clip rules (Sume beta)
Sume's beta Avatar Face Swap applies a ready avatar face to a public 4-15 second source video with audio. Required fields, quality tiers and what it rejects.
- HeyGen lipsync captions are always on; Sume's are opt-in
HeyGen deprecated enable_caption on translation and lipsync and now always returns SRT and VTT. Sume avatar videos burn captions only when you ask for them.
- Holiday avatar ad roster: 3 presenters x 4 scripts, cost by tier
Three reusable avatars and four holiday scripts make 12 clips. Priced on standard, plus and max with a product image; the avatars cost $2.85.
- LemonSlice API: image plus streaming audio vs Sume lip sync
LemonSlice drives a live avatar from an image and streaming audio. For a recorded line, Sume's lip sync takes a still plus an audio_url and returns a file.
Written by Sume