---
title: "A generation failed (data.error says why; data.credits_charged is 0 when it was refunded)."
method: POST
path: "generation.failed"
---

# A generation failed (data.error says why; data.credits_charged is 0 when it was refunded).

`POST generation.failed` (webhook)

POSTed as JSON to every endpoint registered with POST /v1/webhook-endpoints, for every generation on the account (web, API, MCP, CLI; data.origin says which) started after the endpoint was created. Endpoints are managed with an API key only: an app connected through sign-in (OAuth) gets 403 `api_key_required`. Headers: Lumeta-Event-Id (evt_..., also body.id), Lumeta-Event-Type, `Lumeta-Signature: t=<unix seconds>,v1=<hex>`, where the hex is the HMAC-SHA256 of `"<t>.<raw body>"` keyed with the endpoint secret. Verify the signature over the raw bytes and refuse a t older than 5 minutes. Answer any 2xx within 5 seconds; anything else (a redirect, a timeout, a 4xx or 5xx) is retried after 1 min, 5 min, 30 min, 2 h and 12 h, with the same event id. An event goes out a few seconds after its generation finishes (within about a minute for one that ran longer than two hours), not necessarily in order; a retry carries the generation as it is then.

## Payload

- WebhookEvent
  - `id` string, required: evt_...: the same for every attempt and every endpoint of one event (also in Lumeta-Event-Id). Deduplicate on it.
  - `object` 'event'
  - `type` 'generation.succeeded' | 'generation.failed' | 'webhook.test', required
  - `created_at` string
  - `attempt` integer: 1 for the first delivery, then 2 to 6 for the retries.
  - `data` union, required: The generation as it is when this attempt is sent (GET /v1/generations/{id}). Act on data.status.
    - Generation
      - `id` string
      - `status` 'queued' | 'processing' | 'succeeded' | 'failed'
      - `status_message` string, nullable: While `status` is queued or processing, one plain sentence on what the generation is waiting on, when there is a long wait worth telling the person about (today: Seedance clearing new face photos, which can take several minutes, the photos waiting in line while Seedance's face library is busy, a slow hand-over to Seedance, or the safety check after Seedance did not answer). It changes as the wait moves on, and is null otherwise and once the generation has finished. Show it as it is.
      - `model` object
        - `id` integer
        - `name` string
      - `project` string, nullable
      - `input` object
      - `outputs` object[]
        - `index` integer: This output's number, counting from 0: what `gen_<id>#<n>` uses. It never shifts, so the list has a gap where the owner deleted an image.
        - `type` 'image' | 'video' | 'audio' | 'other'
        - `url` string: Durable https URL of the stored file.
        - `mime` string, nullable
        - `width` integer
        - `height` integer
        - `duration` number: Seconds, for audio and video.
        - `size` integer: Bytes.
      - `text` string, nullable: The words of a model that answers in text rather than a file, such as Speech-to-Text's transcript (its `outputs` is empty); null otherwise.
      - `credits_charged` integer: What was charged; a refunded failure reads 0.
      - `error` object, nullable
        - `code` string: canceled when you canceled it; otherwise the error codes above.
        - `message` string: Plain text, written for the person who made the generation.
      - `origin` 'web' | 'api' | 'mcp' | 'cli'
      - `request_id` string, nullable
      - `created_at` string
      - `completed_in_seconds` number, nullable
      - `finalize_1080p` object: Present only while this generation can still be re-made at 1080p with POST /v1/generations/{id}/finalize (Seedance 2.5 480p, for 7 days). The 1080p run is the same video rather than an upscale, and costs what generating it at 1080p would have cost in the first place, so making a 480p one first and then this is about 19% more than going straight to 1080p, and nothing at all when the draft is not worth finishing.
        - `credits` integer: What the 1080p version will cost.
        - `expires_at` string: ISO 8601, UTC. After this the 480p generation can no longer be re-made.
      - `credits_remaining` integer: On POST /v1/generations only.
      - `cap_remaining` integer, nullable
    - object
      - `object` 'webhook_test'
      - `endpoint` string
      - `message` string

## Acknowledgement `2XX`

Received. The body is ignored (only the first 4 KB are read).

---

[API](https://skmtc.dev/lumeta/apis/lumeta-api.md) · [All operations](https://skmtc.dev/lumeta/apis/lumeta-api/llms.txt) · [OpenAPI document](https://skmtc.dev/lumeta/apis/lumeta-api/revisions/9b84c0fe886e?raw)
