Webhook payloads
The HTTP request Mynth sends when a task settles. Headers, signature, event names, and the body for each task type.
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.
POST https://your-app.com/api/mynth-webhook
Content-Type: application/json
User-Agent: Mynth-Webhook/1.0 (+https://mynth.io)
X-Mynth-Event: task.image.generate.completed
X-Mynth-Delivery: tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP:dashboard:wbh_01KE7Y0C3D7XH2M9QK4VZ8TBN5:task.image.generate.completed
X-Mynth-Signature: t=1783159212,v1=5f0c6e...
{"event":"task.image.generate.completed","task":{"id":"tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP"},"request":{...},"result":{...}}Headers#
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Mynth-Webhook/1.0 (+https://mynth.io) |
X-Mynth-Event | The event name. Always equal to event in the body |
X-Mynth-Delivery | A stable ID for this task, endpoint and event. Retries repeat it |
X-Mynth-Signature | t=<unix seconds>,v1=<hex>. Only on endpoints registered in the dashboard or with POST /webhook |
X-Mynth-Delivery#
The value has four parts separated by ::
<task id>:<dashboard|custom>:<endpoint key>:<event>The endpoint key is the wbh_ id for a registered endpoint, or the first 16
hex characters of the URL's SHA-256 for a
per-request URL. Every retry
of one delivery sends the same value. Store it and skip any value you have
already handled. Treat it as an opaque string and do not parse it.
Signature#
A registered endpoint gets a wbs_ secret when it is created. Every delivery
to it is signed with that secret:
X-Mynth-Signature: t=<unix seconds>,v1=<hex>
v1 = hex( HMAC-SHA256( key = your full wbs_... secret,
message = "<t>." + raw request body ) )- The key is the whole secret string,
wbs_prefix included. - The message is
t, a literal., 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 fresh
t. A five-minute tolerance ontrejects replays and still accepts late retries. - Compare in constant time. Mynth sends one
v1today. If the header ever carries several, accept the delivery when any of them matches, as the SDK does.
Per-request webhook.custom URLs get no signature. Anyone who knows the URL can
post to it.
The SDK helpers verify all of this for you. Node.js code and a language-neutral version are on webhooks.
Events#
The delivered event is always the exact name, task.<type>.<status>:
| Task type | Completed | Failed |
|---|---|---|
image.generate | task.image.generate.completed | task.image.generate.failed |
image.remove_background | task.image.remove_background.completed | task.image.remove_background.failed |
image.rate | task.image.rate.completed | task.image.rate.failed |
image.alt | task.image.alt.completed | task.image.alt.failed |
image.review | task.image.review.completed | task.image.review.failed |
video.generate | task.video.generate.completed | task.video.generate.failed |
An endpoint can also subscribe to task.completed, task.failed or all.
Those decide what reaches the endpoint, not what it 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 still gets one delivery.
Branch on event, or on X-Mynth-Event if you want to route before reading
the body. all also covers task types added later, so handle unknown events by
answering 2xx and ignoring them.
Body#
{
"event": "task.image.alt.completed",
"task": { "id": "tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP" },
"request": { "url": "https://assets.example.com/lighthouse.webp" },
"result": { "alt": "A white lighthouse on dark rocks, lit by an orange sunset sky" }
}| Field | Type | Present | Notes |
|---|---|---|---|
event | string | Always | See events |
task.id | string | Always | The tsk_ ID. The only field under task |
request | object | Always | The task's request, defaults filled in, metadata unchanged when the type takes it |
result | object | .completed | Same shape as result on the task object |
errors | [{ code, message? }] | .failed | At least one entry. Codes are on errors |
A completed payload has no errors key and a failed one has no result key.
The key is missing, not null.
The body has no status, cost, apiKeyId, or timestamps. If you need them,
call GET /tasks/{id} with task.id.
To match the task to your own records, read request.metadata. You set it
when you created the task, and it comes back unchanged. Only image generate,
remove background, and video generate take metadata. For rate, alt, and
review, store the taskId from the create response instead.
URLs in request.webhook.custom are shortened to scheme, host and the first
12 characters of the path, the same as on GET /tasks/{id}. A token in a
per-request URL never reaches your other endpoints.
Examples#
{
"event": "task.image.generate.completed",
"task": { "id": "tsk_01KE7XWWEQ4MCGWKBQKJ1G47RP" },
"request": {
"model": "black-forest-labs/flux.2-pro",
"prompt": "A lighthouse at dusk, film grain",
"count": 2
},
"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."
}
}
]
}
}task.image.generate.completed and task.video.generate.completed do not mean the media exists.
Walk result.images or result.videos and check each status. A completed task can hold only
failed items.
TypeScript types#
The SDK exports the whole payload as a union discriminated on event:
import type { MynthSDKTypes } from "@mynthio/sdk";
function handle(payload: MynthSDKTypes.WebhookPayload) {
switch (payload.event) {
case "task.image.generate.completed":
for (const image of payload.result.images) {
if (image.status === "success") console.log(image.url ?? image.mynth_url);
}
break;
case "task.image.generate.failed":
console.error(payload.task.id, payload.errors);
break;
}
}Each variant also has its own name, such as
MynthSDKTypes.WebhookTaskImageAltCompletedPayload. The framework helpers in
@mynthio/sdk/next, /tanstack-start and /convex pass these types to
your handlers already.
Responding#
Answer any 2xx within 30 seconds, then do the slow work. Mynth does not
follow redirects and ignores the response body. Anything other than a 2xx
gets retried with the same X-Mynth-Delivery. The full schedule and ordering
rules are in delivery behavior.
Next steps#
- Webhooks: register an endpoint and verify deliveries.
- The task object: every field inside
result.