Events and payload
A webhook is Esfera calling your server when a conversion changes, so you do not have to poll. This page lists every event the API sends, when each one fires, the exact body and headers of a delivery, and what to know about which events you receive. To trust a delivery you must verify its signature, and because deliveries can repeat you should read Retries and replay.
Registering an endpoint
curl -X POST https://api.esfera.audio/v1/webhooks \
-H "Authorization: Bearer $ESFERA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/esfera"}'{ "id": "6c1e0f6a-2d34-4c55-8a09-1f7d3b9e2a41", "url": "https://example.com/esfera", "secret": "whsec_esf_…" }The secret is returned once, in this response, and never again. Store it where your receiver can read it. It is the key you verify signatures with. An account can have up to three active endpoints, and the URL must be https. The rules are in Retries and replay. The SDK call is esfera.webhooks.create({ url }).
The events
| Event | Fires when | status in the payload |
|---|---|---|
conversion.analyzed | Analysis has finished. This is the signal to choose a preset in the two-step flow. It also fires in the auto-start flow, just before the encode begins. | analyzed |
conversion.completed | The encode finished and the output can be downloaded. | ready |
conversion.failed | The job failed, in analysis or in the encode. errorMessage says why. | error |
conversion.expiring | The output will be removed within 24 hours. See Retention. | ready, or error for a failed job |
Notes:
conversion.analyzedis sent every time a conversion reachesanalyzed, whichever flow it is in.conversion.failedcovers every way a job ends inerror: a failed analysis, a failed encode, an auto-start the server refused after analysis (for example a preset that cannot take the source), and a job the server timed out. The event for a timed-out job can arrive a little after the status changes.conversion.completedandconversion.failedare terminal. Each fires once per conversion.conversion.expiringfires once per conversion, only for conversions that have anexpiresAt.- There is no event for
waiting,queuedorrunning. UseGET /v1/conversions/{id}if you want to follow those.
Which conversions send events
Events belong to the account, not to an API key. Every active endpoint on the account receives the events for every conversion on that account, including conversions made in the Esfera web app, except conversion.expiring, which only API conversions have. An endpoint registered after an event happened does not receive it. A revoked endpoint receives nothing further.
The request
Each delivery is an HTTPS POST to your URL.
| Part | Value |
|---|---|
| Method | POST |
Content-Type | application/json |
X-Esfera-Event | The event name, repeated from the body. |
X-Esfera-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256>. See Verifying signatures. |
User-Agent | esfera-webhooks/1 |
| Timeout | Esfera waits 10 seconds for your response. |
| Success | Any 2xx response. Anything else, or no answer in time, is a failed attempt and is retried. |
The body is JSON with the same envelope for every event:
| Field | Type | Meaning |
|---|---|---|
event | string | One of the four event names. |
createdAt | string, date-time | When the event was created. |
data.conversion | object | The public Conversion, the same shape GET /v1/conversions/{id} returns, so an event and a read can never disagree about a field. |
The conversion is a snapshot taken when the event was created. A retry or a replay delivers that snapshot again, not the conversion's current state. If you need the latest, call GET /v1/conversions/{id}.
Respond with a 2xx as soon as you have verified the signature and stored the event. Do the real work afterwards. A response slower than 10 seconds counts as a failure and the event will arrive again.
Examples
conversion.analyzed
{
"event": "conversion.analyzed",
"createdAt": "2026-09-02T10:15:00Z",
"data": {
"conversion": {
"id": "3f2b8c1e-5a47-4d0e-9c1a-7b6e2d8f4a10",
"name": "master.wav",
"status": "analyzed",
"stage": "analyzed",
"progress": 0,
"sourceDurationSec": 1800,
"outputDurationSec": null,
"outputChannels": 14,
"objectCount": 2,
"outputLoudnessLufs": null,
"outputSizeBytes": null,
"errorMessage": null,
"createdAt": "2026-09-02T10:15:00Z",
"updatedAt": "2026-09-02T10:16:00Z",
"programmeCount": 1,
"preset": "esfera",
"programme": null,
"expiresAt": null,
"profile": "advanced-2",
"outputProfile": null,
"source": {
"kind": "adm",
"order": null,
"normalization": null
},
"output": null
}
}
}conversion.completed
{
"event": "conversion.completed",
"createdAt": "2026-09-02T10:15:00Z",
"data": {
"conversion": {
"id": "3f2b8c1e-5a47-4d0e-9c1a-7b6e2d8f4a10",
"name": "master.wav",
"status": "ready",
"stage": "done",
"progress": 100,
"sourceDurationSec": 1800,
"outputDurationSec": 1800,
"outputChannels": 14,
"objectCount": 2,
"outputLoudnessLufs": -23.1,
"outputSizeBytes": 612345678,
"errorMessage": null,
"createdAt": "2026-09-02T10:15:00Z",
"updatedAt": "2026-09-02T10:41:00Z",
"programmeCount": 1,
"preset": "esfera",
"programme": null,
"expiresAt": "2026-09-05T10:41:00Z",
"profile": "advanced-2",
"outputProfile": "advanced-1",
"source": {
"kind": "adm",
"order": null,
"normalization": null
},
"output": {
"profile": "advanced-1",
"channels": 14,
"elementCount": 3,
"foldedObjects": 0,
"elements": [
{
"kind": "bed",
"label": "Bed",
"layout": "7.1.4",
"channels": 12
},
{
"kind": "object",
"label": "Narrator",
"layout": null,
"channels": 1
},
{
"kind": "object",
"label": "Effects",
"layout": null,
"channels": 1
}
],
"mixPresentations": [
{
"label": "Default",
"language": "en",
"elements": 3
}
]
}
}
}
}conversion.failed
{
"event": "conversion.failed",
"createdAt": "2026-09-02T10:15:00Z",
"data": {
"conversion": {
"id": "3f2b8c1e-5a47-4d0e-9c1a-7b6e2d8f4a10",
"name": "master.wav",
"status": "error",
"stage": "encoding",
"progress": 40,
"sourceDurationSec": 1800,
"outputDurationSec": null,
"outputChannels": 14,
"objectCount": 2,
"outputLoudnessLufs": null,
"outputSizeBytes": null,
"errorMessage": "IAMF profile mismatch: this master has discrete objects and needs Advanced-1 or Advanced-2",
"createdAt": "2026-09-02T10:15:00Z",
"updatedAt": "2026-09-02T10:30:00Z",
"programmeCount": 1,
"preset": "esfera",
"programme": null,
"expiresAt": "2026-09-05T10:30:00Z",
"profile": "advanced-2",
"outputProfile": null,
"source": {
"kind": "adm",
"order": null,
"normalization": null
},
"output": null
}
}
}A failed job also carries an expiresAt: its source is removed on the same 72-hour schedule as a finished job's output.
conversion.expiring
{
"event": "conversion.expiring",
"createdAt": "2026-09-02T10:15:00Z",
"data": {
"conversion": {
"id": "3f2b8c1e-5a47-4d0e-9c1a-7b6e2d8f4a10",
"name": "master.wav",
"status": "ready",
"stage": "done",
"progress": 100,
"sourceDurationSec": 1800,
"outputDurationSec": 1800,
"outputChannels": 14,
"objectCount": 2,
"outputLoudnessLufs": -23.1,
"outputSizeBytes": 612345678,
"errorMessage": null,
"createdAt": "2026-09-02T10:15:00Z",
"updatedAt": "2026-09-04T12:00:00Z",
"programmeCount": 1,
"preset": "esfera",
"programme": null,
"expiresAt": "2026-09-05T10:41:00Z",
"profile": "advanced-2",
"outputProfile": "advanced-1",
"source": {
"kind": "adm",
"order": null,
"normalization": null
},
"output": {
"profile": "advanced-1",
"channels": 14,
"elementCount": 3,
"foldedObjects": 0,
"elements": [
{
"kind": "bed",
"label": "Bed",
"layout": "7.1.4",
"channels": 12
},
{
"kind": "object",
"label": "Narrator",
"layout": null,
"channels": 1
},
{
"kind": "object",
"label": "Effects",
"layout": null,
"channels": 1
}
],
"mixPresentations": [
{
"label": "Default",
"language": "en",
"elements": 3
}
]
}
}
}
}The sample values are illustrative. The shape is the contract.
The delivery log
Every delivery is recorded. GET /v1/webhooks/{id}/deliveries lists them newest first, 25 to a page by default and up to 100.
| Field | Meaning |
|---|---|
id | The delivery id, used to replay it. |
conversionId | The conversion, or null once that conversion is deleted. The delivery outlives it. |
event | The event name. |
status | pending while it is queued or being retried, delivered after a 2xx, failed when the schedule is exhausted. |
attempts | How many times Esfera has tried, over the delivery's whole life. |
lastStatus | The HTTP status your endpoint last returned, or null. |
lastError | The last failure as text, or null. |
createdAt, deliveredAt | When it was created, and when it was delivered. |
There is at most one delivery per endpoint, conversion and event. That is why the delivery log is a reliable record of what you were sent.
Related
- Webhooks reference for the endpoint operations.
- Verifying signatures.
- Retries and replay.