api reference

webhooks

terse machine contract. for the walkthrough with reasoning and per-language signature-verify code, see the webhooks guide.

attach

add callback_url when you create a generation. one gen → one POST on terminal state.

POST https://app.maginary.ai/api/gens/
Content-Type: application/json
Authorization: Bearer <API_KEY>

{
  "prompt": "cyberpunk samurai --ar 16:9",
  "callback_url": "https://your-app.example.com/webhooks/maginary"
}

URL must be https://. loopback / private / metadata IPs are rejected at delivery time.

request

fieldvalue
methodPOST
content-typeapplication/json; charset=utf-8
user-agentmaginary-webhook/1
timeout budget10 seconds per attempt

headers

headervalue
X-Maginary-Eventgen.done | gen.failed
X-Maginary-Event-Idgen uuid. stable across retries. dedupe key. mirrored at body.uuid.
X-Maginary-Delivery-Attemptinteger, 1-indexed. debug only. do not dedupe on this.
X-Maginary-Signaturesha256=<hex> — HMAC-SHA256 of raw body bytes, keyed by your account webhook_secret.

payload (abbreviated)

same envelope as GET /api/gens/{uuid}/. full schema in the openapi spec at /docs/api.

{
  "uuid": "0c8c1f3a-...",
  "processing_state": "DONE" | "FAILED",
  "processing_result": {
    "job_id": "mj_...",
    "slots": [
      { "index": 0, "status": "success" | "failed", "url": "https://...", "charged": true }
    ],
    "error_message": null | string,
    "available_actions": { "0": ["upscale", "vary", ...], "global": ["reroll"] }
  },
  "image_urls": ["https://..."],
  "prompt": "...",
  "action_type": "txt2img" | "img2img" | "txt2vid" | "img2vid" | ...,
  "expected_output_count": 1 | 2 | 3 | 4,
  "created_at": "2026-06-01T12:34:50.112Z",
  "processing_started_at": "2026-06-01T12:34:51.004Z",
  "processing_ended_at":   "2026-06-01T12:35:18.776Z"
}

retry policy

retryable: 5xx, 408, 429, network / timeout. other 4xx = give up (receiver refused).

attemptwait before
1immediate
21 min
35 min
430 min
52 h
68 h

total budget ~10h45m. after that, fall back to polling GET /api/gens/{uuid}/.

signature

signature = "sha256=" + hex(HMAC_SHA256(webhook_secret, raw_body_bytes))
verify   : constant_time_equal(header_value, signature)

hash the raw request bytes, not a re-serialized JSON. use hmac.compare_digest / crypto.timingSafeEqual / hmac.Equal. secret lives at GET /api/api-keys/webhook_secret.

retention

  • successful delivery state (timestamps, attempt count, last status) kept 30 days then nulled.
  • callback_url also cleared at 30-day mark.
  • undelivered / exhausted retries kept indefinitely — forensics stays intact.

need the reasoning, per-language verify code, ngrok testing, and troubleshooting? read the guide →