Chain Ideogram 4.5 edits with webhooks: job.completed starts pass 2
Run a multi-turn Ideogram 4.5 edit chain on Sume without polling: submit with mode webhook, verify the signature, and start the next pass from job.completed.

To run a multi-turn Ideogram 4.5 edit chain without polling, submit pass 1 to POST /v1/images with mode: "webhook" and a public HTTPS webhook_url; when Sume sends job.completed, read the image URL from payload.artifacts[0].url and submit pass 2 with that URL as the new first input_references image. Each pass is a separate job and a separate charge ($0.0375 at low, $0.075 at medium, $0.275 at high on Sume), so the receiver must be idempotent.
Multi-turn editing is the point of the model. Ideogram's launch post says 4.5 "eliminates artifact buildup, making multi-turn editing possible" (Ideogram on X, read 2026-10-05). A chain of five small edits is the natural test of that claim, and webhooks keep the chain running while your process is idle.
What does Sume send, and how do I verify it?
Per the Webhooks docs, Sume sends terminal job events only: job.completed, job.failed and job.canceled. It signs <timestamp>.<raw_body> with HMAC SHA-256 and puts sume-v1=<hex> in x-sume-webhook-signature, with the timestamp in x-sume-webhook-timestamp. During a secret rotation the header holds one entry per live secret, so accept the delivery if any entry matches. Reject a stale timestamp; five minutes is the documented default.
The verifier below refuses to run with an empty secret. Without that check, an unset environment variable turns your HMAC key into an empty string that anyone can sign with.
import hashlib, hmac, os, time
SECRET = os.environ.get("SUME_COM_WEBHOOK_SIGNING_SECRET", "")
def verify(raw: bytes, ts: str, sig_header: str, tol: int = 300) -> bool:
if not SECRET:
return False # fail closed: never verify against an empty secret
try:
if abs(time.time() - int(ts)) > tol:
return False
except ValueError:
return False
digest = hmac.new(SECRET.encode(), ts.encode() + b"." + raw,
hashlib.sha256).hexdigest()
want = "sume-v1=" + digest
return any(hmac.compare_digest(p.strip(), want)
for p in sig_header.split(","))How does the next pass start?
Store each submitted job in a table keyed by job_id: the chain id, the pass number and the prompt list. On job.completed, look up the row, and if it is not already marked done, submit the next pass. The documented rule is to use job_id as your idempotency key, because Sume retries up to 10 attempts at a fixed spacing of 30 seconds by default when your endpoint fails to answer with a 2xx.
The submit call carries its own Idempotency-Key built from the chain and the pass number. If your handler crashes after submitting and Sume redelivers the event, the repeat submit returns the original job and does not bill again.
import json, os, urllib.request
HOOK = "https://example.com/hooks/sume"
def submit_pass(chain: str, n: int, source_url: str, prompt: str) -> str:
body = {
"model": "ideogram/ideogram-v4.5",
"quality": "low",
"prompt": prompt,
"input_references": [{"type": "image_url",
"image_url": {"url": source_url}}],
"mode": "webhook",
"webhook_url": HOOK,
}
req = urllib.request.Request(
"https://api.sume.com/v1/images", json.dumps(body).encode(),
{"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": f"{chain}-pass-{n}"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)["data"]["job"]["id"]
# on job.completed: url = event["payload"]["artifacts"][0]["url"]
# then submit_pass(chain, n + 1, url, PROMPTS[n])What does a five-pass chain cost?
All five passes in one chain are single-image edits, so the cost is five times the per-edit price at whichever quality you pick. Mixing qualities is allowed: draft the early passes at low and make the last one medium.
| Plan | Passes | Total at list x 1.25 |
|---|---|---|
| All low | 5 x $0.0375 | $0.1875 |
| 4 low + 1 medium | 4 x $0.0375 + $0.075 | $0.225 |
| All medium | 5 x $0.075 | $0.375 |
| All high | 5 x $0.275 | $1.375 |
Keep polling as a backup
A webhook is a delivery optimization, not your only recovery path. After ten refused attempts the job is still finished, but you have a failed delivery. Keep a sweeper that reads GET /v1/jobs/{id}/status for any pass older than a few minutes, and use POST /v1/jobs/{job_id}/webhook/redeliver (scope jobs:write) when the endpoint was down and is back. Details are in Jobs and results.
Two more rules from the docs apply here. Webhook URLs must be public HTTPS, so localhost fails at submit. And a job.failed event ends the chain: do not start the next pass from a failed one, and do not resubmit the same paid request unless your idempotency key is the same.
What should the chain do when a pass fails?
A job.failed or job.canceled event ends that branch of the chain. Record the job id and the pass number, stop submitting later passes built on it, and decide whether a human or a retry handles it. Never send the next pass on a source you have not received as completed.
Keep status polling as a backup. If a webhook is lost after the delivery attempts, a poll of the job's status_url tells you whether it finished, and the POST /v1/jobs/{job_id}/webhook/redeliver call can replay an event. Use job_id as your dedupe key, so a replay never starts the next pass twice.
Sources
Related posts
More in Developers
- Ideogram 4.5 seed on Sume returns 400: how to repeat an edit
Ideogram's own API takes a seed for 4.5 edits, but Sume returns 400 unsupported_parameter for seed on every image model. Keep the output URL, not the seed.
- Ideogram 4.5 edit in TypeScript: handle 200, 202 and 502 on Sume
One fetch helper for POST /v1/images with Ideogram 4.5: return the URL on 200, poll status_url and result_url on 202, and throw the error body on 502.
- Ideogram 4.5 on Sume does not accept output_format
Ideogram 4.5's output format is chosen by the provider on Sume. Which other image ids take png, jpeg or webp, and how to convert after the fact in Python.
- Ideogram 4.5 transparency claim vs Sume's background field
Higgsfield lists transparent backgrounds for Ideogram 4.5. On Sume, background works only on ChatGPT Image 2.5; other models need RMBG or Image 1.0.
Written by Sume