Seedance 2.5 job failed: refund, new idempotency key, and rerun cost

A failed Seedance 2.5 job is refunded on Sume. Retry with a new Idempotency-Key; the old one replays the failed job. A Python handler and the rerun cost.

6 min readSume
All posts

When a Seedance 2.5 job fails on Sume, the reserved amount can be refunded on failure, per the Sume workflow docs,, and a retry needs a new Idempotency-Key, because a replay with the old key returns the original job, not a new one. A 30-second 720p clip reserves $17.34 at submit, so the refund path matters. The pattern is to receive the job.failed webhook, verify it, then resubmit with a key such as <job_id>-retry1.

The refund and idempotency rules are in the video docs, and the signature scheme is in the webhooks guide. Seedance 2.5 limits are in the Video Router docs.

What a retry costs

A refunded failure costs nothing, so the retry is the only new spend. Dropping to 480p for the retry turns a long clip into a cheap check.

Where the numbers come from: Sume prices Seedance by video tokens, computed as width x height x seconds x 24 fps / 1024, at a provider list rate of $0.0214 per 1,000 tokens for 2.5 (the 1080p tier is $0.0234), times the 1.25 house margin that the Video Router docs describe, rounded up to the cent. A 16:9 720p frame is 1280 x 720, so one second is 21,600 tokens and a 30-second clip is 648,000 tokens, or $13.87 at list and $17.34 billed. Use the same arithmetic to predict a rerun before you submit it, and remember that a retry at a lower resolution is a different price, not a discount on the first attempt.

Computed from the Sume pricing tables for 16:9 Seedance 2.5 clips, read 2026-10-06.
Length480p720p1080p
4 s$1.08$2.32$5.69
15 s$4.04$8.67$21.33
30 s$8.07$17.34$42.65

The handler

This verifies the sume-v1 signature, rejects an empty secret and a timestamp more than 300 seconds off, then branches on the event. Run it as is: the demo at the bottom signs a failed event, then shows an empty secret being rejected.

import hashlib, hmac, json, time

def verify(raw: bytes, ts: str, sig: str, secret: str) -> bool:
    if not secret or not ts or not sig:
        return False
    if abs(time.time() - int(ts)) > 300:
        return False
    mac = hmac.new(secret.encode(), ts.encode() + b"." + raw, hashlib.sha256)
    want = "sume-v1=" + mac.hexdigest()
    return any([hmac.compare_digest(want, c.strip()) for c in sig.split(",")])

def handle(raw: bytes, ts: str, sig: str, secret: str) -> str:
    if not verify(raw, ts, sig, secret):
        return "401 rejected"
    evt = json.loads(raw)
    if evt["event"] == "job.completed":
        return "save " + evt["payload"]["artifacts"][0]["url"]
    return "retry with key " + evt["job_id"] + "-retry1"

if __name__ == "__main__":
    body = json.dumps({"event": "job.failed", "job_id": "job_1", "status": "ERROR"}).encode()
    ts = str(int(time.time()))
    sig = "sume-v1=" + hmac.new(b"s3cret", ts.encode() + b"." + body, hashlib.sha256).hexdigest()
    print(handle(body, ts, sig, "s3cret"))
    print(handle(body, ts, sig, ""))

Why a new key

An Idempotency-Key makes retries safe: a network timeout on submit can be repeated without making two jobs. The same safety means that reusing the key after a failure returns the failed job. So derive the key from the job id and a retry counter, and stop at a small number of tries. If a clip fails twice at the same settings, change something: shorten it, lower the resolution or simplify the prompt.

Do not treat job.canceled as a failure to retry. A cancel is a deliberate stop, so branch on the event name and leave it alone unless you know why it happened.

Webhooks come from the callback_url field on POST /v1/videos, which must be HTTPS; this post covers it. For the shape of failed and canceled events, see the event-branch post, and for where failures send their webhook, the failure webhook post. Before a retry of a long clip, run the request against the catalog with the pre-submit check, since a value outside the catalog is refused rather than rerendered.

Cap the retries

Set a limit of two retries per job id and log each one with the key and the settings. If the third attempt would be the same request, stop and look at the failure reason in the payload's error object. A prompt that fails the same way twice is a prompt problem, not a transient one. Because failures are refunded, the cost of this discipline is time, not money. Because successes are not, the cost of an unbounded loop is real, since a loop that keeps producing finished clips you never read will spend credits on every pass.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume