GET /v3/jobs/{job_id} until a job finishes, register a webhook and Hedra POSTs the result to you. A job fires exactly one terminal event — job.completed or job.failed — chosen from its final status when the delivery goes out.
Register a webhook
There are two ways to say where deliveries go:- Per job — pass
webhookat submit, next toinput. This URL applies to that job only. - Account default — store one endpoint with
PUT /webhooks/default. It receives the terminal event for every job that names no per-jobwebhook.
enabled: false pauses the default endpoint without discarding the URL; DELETE /webhooks/default removes it entirely. To check the wiring before running a real job, POST /webhooks/default/test fires a test delivery at the stored endpoint and reports what it answered:
The payload
The body is the same envelopeGET /v3/jobs/{job_id} returns, minus its poll-only fields (logs, cost, currency) — poll the job if you need those.
job.failed, status is FAILED and error carries the same error envelope the poll endpoint returns.
Generated media is retained for 48 hours after a job completes, measured
from the original completion time. Download
outputs[].url (or chain
outputs[].asset_id) promptly — a delivery replayed after the window carries
EXPIRED outputs with url: null.Verify the signature
Every delivery is signed with Hedra’s ed25519 key — the same key for every account, so you can fetch it once and cache it:
The signature covers a canonical string of five newline-separated fields, in this order:
Retries and deduplication
Delivery is at-least-once. A2xx from your endpoint is success; anything else — including a redirect, which is never followed — is retried. Hedra makes up to 12 attempts over approximately 6 hours, backing off 10s, 30s, 90s, 4m30s, 13m30s, 40m30s, then hourly. Because the retry window is bounded, acknowledge with a 2xx first and do your own processing asynchronously.
Deduplicate on X-Hedra-Webhook-Id. It identifies the event — it is the job’s own id and is byte-identical across every retry and replay. Do not hash the request body: each attempt re-signs the output URLs, so the body legitimately differs between attempts of the same event.
The one exception is X-Hedra-Webhook-Redelivery: true: an operator asked for this event to be sent again, so process it even if you have already recorded that id — that request is the whole reason it was sent.
Inspect and replay deliveries
GET /v3/webhooks/deliveries lists every delivery with its status (PENDING, DELIVERING, DELIVERED, FAILED), its source (per_job or default), cumulative attempts, the latest outcome (last_response_status, last_error), and its replay history.
FAILED and not retried again. Replay it:
X-Hedra-Webhook-Redelivery: true, and the previous outcome is archived in the delivery’s redeliveries list. A replay answers 409 while a delivery for the job is still in flight.
last_error is a structured error envelope, not free text: a stable code (DEADLINE_EXCEEDED, UNAVAILABLE, RESOURCE_EXHAUSTED, and so on — the same vocabulary the rest of the API uses), a fixed message, and retryable, which tells you whether replaying is likely to help. One code is permanent and stops the ladder immediately: FAILED_PRECONDITION means the URL resolves to a blocked address range (or redirects to one) — fix the URL; replaying will not help. Your endpoint’s URL, headers, and response body are never echoed back, so treat your own logs as the record of what your endpoint returned.