Track Processing Webhook
The Track Processing webhook lets your application react to a track’s outcome without polling. AudioDN sends an HTTP POST request to the webhook URL configured for your organization when a track reaches a terminal status or when its full file set is complete. For per-output progress as each output is produced, use the Output webhook instead.
Configured in the dashboard
The webhook URL is set in your organization settings in the dashboard, not via the API. Leave it blank to disable webhooks. For per-output events see the Output webhook, and for collection create/update/delete events see the Collection Sync webhook.
When it fires
To avoid noise, this webhook does not relay transitional statuses (processing, fallback, fallback_processing) or the initial initialized state. It is delivered only on events of note:
- Terminal status — the track reaches
ready,incomplete,error, orinit_error. - Files complete —
track.files_completed_atis stamped once every output preset (including optional and after-the-fact ones) has been attempted. This can arrive after an earlierready, so the same track may deliver twice: once when it becomes playable, and once when the full file set is done.
A typical track delivers once (status settles to ready and files complete in the same step) or twice (early ready from required presets, then a later files-complete event when optional outputs finish).
- Sent as an HTTP
POSTwithContent-Type: application/json. - Includes an
X-ADN-Event: track.status_changedheader. - Delivery is fire-and-forget / at-least-once — design your handler to be idempotent.
- Event ordering is not guaranteed; use the
status,previous_status, andtrack.files_completed_atfields rather than assuming arrival order. - Requests are currently unsigned. Treat the webhook URL as a secret and, if needed, verify the
track_idagainst the API. - Respond promptly with a
2xxstatus code to acknowledge receipt.
Track statuses
The status field reflects the track’s track_status_id at the time of the event. The possible values are:
| Status | Terminal | Meaning |
|---|---|---|
| initialized | No | The track record was created and is waiting for upload / init. Transitional — not delivered by this webhook. |
| processing | No | The upload completed and AudioDN is generating configured outputs. Transitional — not delivered by this webhook. |
| fallback | No | The primary processor could not handle the file, so it has been queued for the fallback processor. Transitional — not delivered by this webhook. |
| fallback_processing | No | The fallback processor is actively working on the track. Transitional — not delivered by this webhook. |
| ready | Yes | A required playable output succeeded. Optional outputs (including transcription) may still be running until files_completed_at is set. |
| incomplete | Yes | A playable file exists, but a required output preset did not succeed at final settle. The track may still be playable. |
| error | Yes | Processing failed and no playable track could be produced. |
| init_error | Yes | The track could not be initialized (for example, the upload was invalid or unreadable). No processing occurred. |
Terminal statuses (ready, incomplete, error, init_error) do not change again. After an early ready, files_completed_at can still be stamped later and can trigger a second delivery of this webhook. The remaining statuses are transitional and are not sent.
Payload schema
Every event delivers the same envelope. Because deliveries are limited to terminal outcomes and the files-complete event, the files array is normally populated. track.files_completed_at is null on an early ready (optional outputs still running) and a timestamp once the full set is done. Each file includes is_success and a nested variant object (the historical name for the output preset that produced it); a failed output appears with is_success: false, no url, and the reason in status_text.
{
"event": "track.status_changed",
"status": "ready | incomplete | error | init_error",
"previous_status": "string | null",
"organization_id": "uuid",
"creator_id": "uuid | null",
"collection_id": "uuid",
"track_id": "uuid",
"upload_session_id": "uuid | null",
"track": {
"id": "uuid",
"track_status_id": "string",
"upload_session_id": "uuid | null",
"index": "string",
"organization_index": "string | null",
"file_name": "string | null",
"file_name_original": "string | null",
"player_title": "string | null",
"player_subtitle": "string | null",
"player_color": "string | null",
"player_color_light": "string | null",
"player_color_dark": "string | null",
"order": "number",
"format": "object | null",
"metadata": "object | null",
"duration": "number | null",
"size": "number | null",
"files_completed_at": "string | null",
"cloudflare_image_id": "uuid | null",
"cover_image": {
"icon": { "type": "icon", "width": 80, "height": 80, "url": "string" },
"small": { "type": "small", "width": 200, "height": 200, "url": "string" },
"regular": { "type": "regular", "width": 400, "height": 400, "url": "string" },
"large": { "type": "large", "width": 800, "height": 800, "url": "string" }
},
"theme": "array | null",
"transcription": "string | null",
"transcription_language": "string | null"
},
"files": [
{
"id": "uuid",
"path": "string",
"url": "string | null",
"size": "number | null",
"content_type": "string | null",
"file_name": "string",
"is_public": "boolean",
"is_success": "boolean",
"status_text": "string | null",
"data": "object | null",
"props": "object | null",
"variant": {
"id": "uuid",
"index": "string",
"variant_type": {
"id": "string",
"viewer_id": "string",
"title": "string"
}
}
}
]
} Transcription
track.transcription and track.transcription_language stay null until a transcription output preset has been applied. After that, transcription is plain-text paragraphs and transcription_language is the BCP-47 language (detected automatically unless the preset sets one). The Output webhook delivers the caption file (.vtt) when that output is ready. It does not include the track-level transcription or transcription_language fields.
Cover images
When a track has cover art, track.cover_image provides ready-to-use URLs at each available size, and track.cloudflare_image_id stores the underlying image ID if you need to rebuild a URL later. Both are null until a cover is available (covers are extracted during processing, so early events such as initialized may not include one yet).
Prefer the ready-made cover_image URLs. If you only have the image ID from a previous event, you can construct any size yourself. Each URL follows the pattern below, where <variant> is one of the named sizes:
https://imagedelivery.net/iVHDyjXAr_lt5UUJaLbk1Q/{cloudflare_image_id}/{variant} | Variant | Dimensions | Example URL |
|---|---|---|
| icon | 80 x 80 | …/{cloudflare_image_id}/icon |
| small | 200 x 200 | …/{cloudflare_image_id}/small |
| regular | 400 x 400 | …/{cloudflare_image_id}/regular |
| large | 800 x 800 | …/{cloudflare_image_id}/large |
The same scheme applies to collection cover art delivered by the Collection Sync webhook.
When a cover is processed, ADN also extracts a color palette from it, delivered as track.theme — an array of color objects with hex, area, lightness, and saturation values (null until a cover has been processed). This is the same theme field returned by the Tracks API.
Example payloads
Early ready (required variants done)
Sent as soon as the required variants succeed and the track is playable, while optional variants are still processing. files_completed_at is null because the full file set is not done yet. A track with no optional variants skips straight to the settled event below and never sends this.
{
"event": "track.status_changed",
"status": "ready",
"previous_status": "processing",
"organization_id": "11111111-1111-1111-1111-111111111111",
"creator_id": null,
"collection_id": "22222222-2222-2222-2222-222222222222",
"track_id": "33333333-3333-3333-3333-333333333333",
"upload_session_id": "44444444-4444-4444-4444-444444444444",
"track": {
"id": "33333333-3333-3333-3333-333333333333",
"track_status_id": "ready",
"upload_session_id": "44444444-4444-4444-4444-444444444444",
"index": "track-index-001",
"organization_index": "your-track-id-001",
"file_name": "interview.mp3",
"file_name_original": "Interview Final.mp3",
"player_title": "Episode 12",
"player_subtitle": "The Interview",
"player_color": "#1DB954",
"player_color_light": "#1BAA4D",
"player_color_dark": "#1DB954",
"order": 0,
"format": { "codec": "aac", "channels": 2, "sample_rate": 44100 },
"metadata": null,
"duration": 1832.5,
"size": 18234123,
"files_completed_at": null
},
"files": [ { "..." : "playable transcode file(s) so far" } ]
} Ready and files complete (settled)
Sent once every variant (required and optional) has been attempted. files_completed_at is stamped and the full file set is present. For a track with no optional variants this is the single delivery you receive.
{
"event": "track.status_changed",
"status": "ready",
"previous_status": "processing",
"organization_id": "11111111-1111-1111-1111-111111111111",
"creator_id": null,
"collection_id": "22222222-2222-2222-2222-222222222222",
"track_id": "33333333-3333-3333-3333-333333333333",
"upload_session_id": "44444444-4444-4444-4444-444444444444",
"track": {
"id": "33333333-3333-3333-3333-333333333333",
"track_status_id": "ready",
"upload_session_id": "44444444-4444-4444-4444-444444444444",
"index": "track-index-001",
"organization_index": "your-track-id-001",
"file_name": "interview.mp3",
"file_name_original": "Interview Final.mp3",
"player_title": "Episode 12",
"player_subtitle": "The Interview",
"player_color": "#1DB954",
"player_color_light": "#1BAA4D",
"player_color_dark": "#1DB954",
"order": 0,
"format": { "codec": "aac", "channels": 2, "sample_rate": 44100 },
"metadata": { "artist": "Acme Media" },
"duration": 1832.5,
"size": 18234123,
"files_completed_at": "2026-01-01T12:00:05.000Z",
"cloudflare_image_id": "77777777-7777-7777-7777-777777777777",
"cover_image": {
"icon": { "type": "icon", "width": 80, "height": 80, "url": "https://imagedelivery.net/iVHDyjXAr_lt5UUJaLbk1Q/77777777-7777-7777-7777-777777777777/icon" },
"small": { "type": "small", "width": 200, "height": 200, "url": "https://imagedelivery.net/iVHDyjXAr_lt5UUJaLbk1Q/77777777-7777-7777-7777-777777777777/small" },
"regular": { "type": "regular", "width": 400, "height": 400, "url": "https://imagedelivery.net/iVHDyjXAr_lt5UUJaLbk1Q/77777777-7777-7777-7777-777777777777/regular" },
"large": { "type": "large", "width": 800, "height": 800, "url": "https://imagedelivery.net/iVHDyjXAr_lt5UUJaLbk1Q/77777777-7777-7777-7777-777777777777/large" }
},
"theme": [
{ "hex": "#1DB954", "area": 0.42, "lightness": 0.45, "saturation": 0.73 },
{ "hex": "#0E1320", "area": 0.31, "lightness": 0.08, "saturation": 0.35 }
]
},
"files": [
{
"id": "55555555-5555-5555-5555-555555555555",
"path": "org/collection/track/transcode.aac",
"url": "https://cdn.audiodn.com/org/collection/track/transcode.aac",
"size": 17012344,
"content_type": "audio/aac",
"file_name": "transcode.aac",
"is_public": true,
"is_success": true,
"status_text": "OK",
"data": null,
"props": null,
"variant": {
"id": "66666666-6666-6666-6666-666666666666",
"index": "transcode-default",
"variant_type": {
"id": "transcode",
"viewer_id": "audio",
"title": "Transcoded Audio"
}
}
}
]
} Incomplete (partial success)
Sent when a playable track was produced but a non-essential variant failed. The track is still usable.
{
"event": "track.status_changed",
"status": "incomplete",
"previous_status": "processing",
"organization_id": "11111111-1111-1111-1111-111111111111",
"creator_id": null,
"collection_id": "22222222-2222-2222-2222-222222222222",
"track_id": "33333333-3333-3333-3333-333333333333",
"upload_session_id": "44444444-4444-4444-4444-444444444444",
"track": {
"id": "33333333-3333-3333-3333-333333333333",
"track_status_id": "incomplete",
"upload_session_id": "44444444-4444-4444-4444-444444444444",
"index": "track-index-001",
"organization_index": "your-track-id-001",
"file_name": "interview.mp3",
"file_name_original": "Interview Final.mp3",
"player_title": "Episode 12",
"player_subtitle": "The Interview",
"player_color": "#1DB954",
"player_color_light": "#1BAA4D",
"player_color_dark": "#1DB954",
"order": 0,
"format": { "codec": "aac", "channels": 2, "sample_rate": 44100 },
"metadata": null,
"duration": 1832.5,
"size": 18234123,
"files_completed_at": "2026-01-01T12:00:05.000Z"
},
"files": [
{
"id": "55555555-5555-5555-5555-555555555555",
"path": "org/collection/track/transcode.aac",
"url": "https://cdn.audiodn.com/org/collection/track/transcode.aac",
"size": 17012344,
"content_type": "audio/aac",
"file_name": "transcode.aac",
"is_public": true,
"is_success": true,
"status_text": "OK",
"data": null,
"props": null,
"variant": {
"id": "66666666-6666-6666-6666-666666666666",
"index": "transcode-default",
"variant_type": {
"id": "transcode",
"viewer_id": "audio",
"title": "Transcoded Audio"
}
}
},
{
"id": "99999999-9999-9999-9999-999999999999",
"path": "org/collection/track/lossless.flac",
"url": null,
"size": null,
"content_type": "audio/flac",
"file_name": "lossless.flac",
"is_public": false,
"is_success": false,
"status_text": "Source is lossy; cannot produce a lossless output",
"data": null,
"props": null,
"variant": {
"id": "88888888-8888-8888-8888-888888888888",
"index": "lossless-download",
"variant_type": {
"id": "transcode",
"viewer_id": "audio",
"title": "Transcoded Audio"
}
}
}
]
} Error (failure)
Sent when processing fails and no playable track could be produced. The files array is empty.
{
"event": "track.status_changed",
"status": "error",
"previous_status": "processing",
"organization_id": "11111111-1111-1111-1111-111111111111",
"creator_id": null,
"collection_id": "22222222-2222-2222-2222-222222222222",
"track_id": "33333333-3333-3333-3333-333333333333",
"upload_session_id": "44444444-4444-4444-4444-444444444444",
"track": {
"id": "33333333-3333-3333-3333-333333333333",
"track_status_id": "error",
"upload_session_id": "44444444-4444-4444-4444-444444444444",
"index": "track-index-001",
"organization_index": "your-track-id-001",
"file_name": "interview.mp3",
"file_name_original": "Interview Final.mp3",
"player_title": "Episode 12",
"player_subtitle": "The Interview",
"player_color": "#1DB954",
"player_color_light": "#1BAA4D",
"player_color_dark": "#1DB954",
"order": 0,
"format": null,
"metadata": null,
"duration": null,
"size": 18234123,
"files_completed_at": "2026-01-01T12:00:05.000Z"
},
"files": []
} Verifying delivery
Each delivery attempt is logged on AudioDN’s side, including the HTTP status code returned by your endpoint. If you stop receiving events, confirm your endpoint returns a 2xx response quickly and that the webhook URL in Settings is correct.
Handling webhooks
A minimal handler reads the status field and reacts to terminal states. Because delivery is at-least-once and unordered, key your logic on track_id + status rather than on receipt order.
app.post('/webhooks/audiodn', (req, res) => {
// Acknowledge quickly, then process asynchronously.
res.sendStatus(200)
const { event, status, track_id } = req.body
if (event !== 'track.status_changed') return
switch (status) {
case 'ready':
// Track is playable. track.files_completed_at === null means optional
// variants are still running (a second delivery will follow once done).
markTrackReady(track_id, req.body.files)
break
case 'incomplete':
// Playable, but some variants failed. Inspect files[].is_success.
markTrackReady(track_id, req.body.files)
flagForReview(track_id)
break
case 'error':
case 'init_error':
// Processing failed; no playable track was produced.
markTrackFailed(track_id)
break
}
// Note: transitional statuses (processing / fallback*) are not delivered.
// For per-file progress, use the Track File webhook instead.
})