Webhooks
The asynchronous generation endpoint (/v1/ideogram-v4/async/generate) returns
immediately with a generation_id and delivers the finished result to the
webhook_url you supply. Ideogram sends a JSON POST to that URL once every image
for the request has finished generating. Each delivery is signed with Ed25519 so
you can confirm it genuinely came from Ideogram before acting on it.
What Ideogram delivers
The request body mirrors the synchronous generation response, so the same handler can process both. It contains:
generation_id: URL-safe base64 ID of the generation. Use it to correlate the webhook with your original API call, and to poll the generation.created: the time the generation was created.data: an array of generated images, each withurl,prompt,resolution,seed, andis_image_safe.
Request headers
Every delivery includes these headers:
Verifying the signature
-
Fetch the public keys. Send a
GETtohttps://api.ideogram.ai/v1/.well-known/jwks.json. The response lists Ed25519 public keys in JWK form. Each key’sxfield is the 32-byte public key, base64url-encoded with no padding. The key set is cacheable; refresh it if a signature ever fails to verify against your cached copy, in case the keys rotated. -
Rebuild the signed message. Concatenate these four values in this exact order, joined with single newline (
\n) separators, then encode the result as UTF-8:Hash the raw body bytes exactly as received. Do not parse and re-serialize the JSON first, or the hash will not match what Ideogram signed.
-
Verify against the published keys. Decode
X-Ideogram-Webhook-Signaturefrom hex and check it against each public key in the set. If any key verifies, the webhook is authentic. If none do, reject the request. TheX-Ideogram-Webhook-Key-Idheader tells you which key to try first, but fall back to the others so signatures stay verifiable across key rotations.
Python example
The body passed to verify_webhook must be the raw request bytes exactly as
Ideogram sent them. Read them from your web framework’s raw-body accessor, not
from a parsed-then-re-serialized JSON object: re-serializing can reorder keys or
change whitespace, which changes the bytes and makes the SHA-256 hash (and so the
signature check) fail. Examples of the raw-body accessor:
- Flask:
request.get_data() - FastAPI / Starlette:
await request.body() - Django:
request.body - Express (Node):
express.raw()middleware, thenreq.body(aBuffer)
A complete Flask handler:
Cache the public keys rather than fetching them on every delivery, and refresh them on a verification failure or on a periodic interval.
Retries, idempotency, and polling
Your endpoint should return a 2xx status to acknowledge a delivery. If it returns
a non-2xx status or times out, Ideogram retries the delivery. Design your handler
for this:
- Make it idempotent. The same
generation_idcan arrive more than once (for example, a retry after your endpoint was briefly slow or unavailable). Key your completion state ongeneration_id, treat an already-processedgeneration_idas a no-op, and return2xxso the retries stop. - Delivery is not guaranteed. Ideogram retries a delivery only a limited number of times before dropping it, so a webhook may never arrive (for example, if your endpoint is down for an extended period). Do not rely on the webhook as your only way to get a result.
- Fall back to polling. If you do not receive a delivery, fetch the result from
the polling endpoint,
GET /v1/generations/{generation_id}, using thegeneration_idthe async endpoint returned. It returns the samedatapayload once the generation has finished.