• Docs
  • API reference
  • SDKs & tools
  • Models
  • Changelog
Sign inGet an API key
DocsAPI referenceSDKs & toolsModelsChangelog

Basics

IntroductionAuthenticationThe task objectWebhook payloadsErrors

Endpoints

POSTGenerate imagePOSTEstimate costPOSTUpload imagesPOSTGenerate alt textPOSTReview imagePOSTRate imagePOSTRemove backgroundPOSTUpscale image
GETList tasksGETGet taskGETGet task statusGETGet task result
  1. API reference
  2. /
  3. Basics

Webhook payloads

The HTTP request Mynth sends when a task settles. Standard Webhooks headers and signature, event names, and the event body.

PreviousThe task objectNextErrors

On this page

Headerswebhook-idSignatureEventsBodyExamplesTypeScript typesRespondingNext steps

When a task settles, Mynth sends one POST to each endpoint that subscribed. This page describes that request exactly. Registering endpoints, receiver code for each framework, and retry behavior are on webhooks.

Deliveries follow Standard Webhooks, so any of its libraries verifies them.

POST https://your-app.com/api/mynth-webhook
Content-Type: application/json
User-Agent: Mynth-Webhook/1.0 (+https://mynth.io)
webhook-id: evt_01KE7Y9M3R8T2QXN5BVC4WJ6HD
webhook-timestamp: 1791108012
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=

{"id":"evt_01KE7Y9M3R8T2QXN5BVC4WJ6HD","type":"task.image.generate.completed","timestamp":"2026-10-04T10:00:12.000Z","data":{...}}

Headers

HeaderValue
Content-Typeapplication/json
User-AgentMynth-Webhook/1.0 (+https://mynth.io)
webhook-idThe event ID, evt_.... The same for every delivery of the event, and equal to the body id
webhook-timestampWhen this attempt was sent, in Unix seconds. Each retry sends a new one
webhook-signaturev1,<base64>. Only on endpoints registered in the dashboard or with POST /webhooks

webhook-id

Every delivery of one event carries the same ID: to each of your endpoints, on every retry, and after Mynth recovers from an outage. Store it and skip any ID you have already handled. Treat it as an opaque string: do not parse it.

An endpoint registered twice with the same URL gets the event twice, with the same ID. Deduplicating drops the second one, which is correct.

Signature

A registered endpoint gets a whsec_ secret when it is created. Every delivery to it is signed with that secret:

webhook-signature: v1,<base64>

signature = base64( HMAC-SHA256( key     = base64-decode( secret without "whsec_" ),
                                 message = "<webhook-id>.<webhook-timestamp>.<raw body>" ) )
  • The key is the bytes the secret encodes, not the secret string.
  • The message is the ID, a ., the timestamp, a ., then the body bytes exactly as received. Parsing and re-serializing the JSON changes the bytes and breaks the match.
  • Each retry is signed again with a new webhook-timestamp. Reject a timestamp more than five minutes from your clock: that stops replays and still accepts late retries.
  • The ID is signed, so a replay with a fresh webhook-id to get past your deduplication fails verification.
  • The header is a space-separated list. Mynth sends one v1 today; accept the delivery when any v1 entry matches. Compare in constant time.

Per-request webhook.custom URLs get no signature. They carry webhook-id and webhook-timestamp but anyone who knows the URL can post to them: put a secret token in the URL and check it. See per-request webhooks.

verifyWebhook() in the SDK, the SDK's framework helpers, and every Standard Webhooks library verify all of this for you.

Events

The event type is always the exact name, task.<type>.<status>:

Task typeCompletedFailed
image.generatetask.image.generate.completedtask.image.generate.failed
image.remove_backgroundtask.image.remove_background.completedtask.image.remove_background.failed
image.upscaletask.image.upscale.completedtask.image.upscale.failed
image.ratetask.image.rate.completedtask.image.rate.failed
image.alttask.image.alt.completedtask.image.alt.failed
image.reviewtask.image.review.completedtask.image.review.failed
video.generatetask.video.generate.completedtask.video.generate.failed

completed means the task finished with results. Items in the result can still have failed: check each one's status. failed means the task failed as a whole and has no results; data.errors says why.

An endpoint can also subscribe to task.completed, task.failed or all. Those decide what reaches the endpoint, not what an event is called. An endpoint on all still receives task.image.alt.completed, never all. An endpoint that matches through more than one of its events gets one delivery.

all also covers task types added later, so answer an unknown type with a 2xx and ignore it.

Body

{
  "id": "evt_01KE7Y9M3R8T2QXN5BVC4WJ6HD",
  "type": "task.image.alt.completed",
  "timestamp": "2026-10-04T10:00:12.000Z",
  "data": {
    "id": "tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP",
    "type": "image.alt",
    "status": "completed",
    "request": { "url": "https://assets.example.com/lighthouse.webp" },
    "result": { "alt": "A white lighthouse on dark rocks, lit by an orange sunset sky" },
    "errors": null,
    "cost": "0.00040000",
    "user_id": "user_01KE7XS3J0V5N8QF2PZ6W4MBRC",
    "api_key_id": "ak_01KE7XT9K2C6M4RX8NBV1QZJ5W",
    "created_at": "2026-10-04T10:00:00.000Z",
    "updated_at": "2026-10-04T10:00:12.000Z"
  }
}
FieldTypeNotes
idstringThe event ID, equal to the webhook-id header
typestringSee events
timestampstringISO 8601. When the event happened: the task's updated_at, not when this attempt was sent
dataobjectThe task exactly as GET /tasks/{id} returns it: same fields, same nulls, redacted

data.request comes back with defaults filled in and metadata unchanged, so match the task to your own records with data.request.metadata. Only image generate, remove background, upscale and video generate take metadata. For rate, alt and review, store the task_id from the create response instead.

URLs in data.request.webhook.custom are cut down to scheme://host, the same as on GET /tasks/{id}, so a token in a per-request URL never reaches your other endpoints.

Examples

These show data only; the envelope around it is the same for every event.

{
  "id": "tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP",
  "type": "image.generate",
  "status": "completed",
  "request": {
    "model": "black-forest-labs/flux.2-pro",
    "prompt": "A lighthouse at dusk, film grain",
    "count": 2,
    "metadata": { "order_id": "ord_42" }
  },
  "result": {
    "model": "black-forest-labs/flux.2-pro",
    "images": [
      {
        "status": "success",
        "id": "img_V1StGXR8Z5jdHi6BmyT0sC1pQ2rN4wLk",
        "url": "https://cdn.mynth.io/images/img_V1StGXR8Z5jdHi6BmyT0sC1pQ2rN4wLk.webp",
        "mynth_url": "https://cdn.mynth.io/images/img_V1StGXR8Z5jdHi6BmyT0sC1pQ2rN4wLk.webp",
        "size": "1536x1024",
        "format": "webp"
      },
      {
        "status": "failed",
        "error": {
          "code": "provider_error",
          "message": "The provider failed to process the request. Please try again."
        }
      }
    ]
  },
  "errors": null,
  "cost": "0.03000000",
  "user_id": "user_01KE7XS3J0V5N8QF2PZ6W4MBRC",
  "api_key_id": "ak_01KE7XT9K2C6M4RX8NBV1QZJ5W",
  "created_at": "2026-10-04T10:00:00.000Z",
  "updated_at": "2026-10-04T10:00:12.000Z"
}

task.image.generate.completed and task.video.generate.completed do not mean the media exists. Walk data.result.images or data.result.videos and check each status. A completed task can hold only failed items.

TypeScript types

verifyWebhook() returns the event typed as a union discriminated on type:

import { verifyWebhook } from "@mynthio/sdk";

const event = await verifyWebhook({ body: await request.text(), headers: request.headers });

switch (event.type) {
  case "task.image.generate.completed":
    for (const image of event.data.result.images) {
      if (image.status === "success") console.log(image.url ?? image.mynth_url);
    }
    break;
  case "task.image.generate.failed":
    console.error(event.data.id, event.data.errors);
    break;
}

The type is MynthSDKTypes.WebhookEvent, and one variant is MynthSDKTypes.WebhookEventOf<"image.alt", "completed">. The framework helpers in @mynthio/sdk/next, /tanstack-start and /convex map completed events to the same results as polling. See receive events.

Responding

Answer any 2xx within 30 seconds, then do the slow work. Mynth does not follow redirects and ignores the response body. Anything else gets retried with the same webhook-id. The full schedule is in delivery behavior.

Next steps

  • Webhooks: register an endpoint and verify deliveries.
  • The task object: every field inside data.