Webhook Track Processing
El webhook Track Processing permite que tu aplicación reaccione al resultado de una pista sin hacer polling. AudioDN envía una solicitud HTTP POST a la URL de webhook configurada para tu organización cuando una pista alcanza un estado terminal o cuando su conjunto completo de archivos está listo. Para el progreso por archivo a medida que se produce cada variante, usa el webhook Track File en su lugar.
Se configura en el panel
La URL del webhook se establece en el panel Settings → Webhook del dashboard, no vía la API. Déjala en blanco para desactivar los webhooks. Para eventos por archivo consulta el webhook Track File, y para eventos de create/update/delete de colección consulta el webhook Collection Sync.
Cuándo se dispara
Para evitar ruido, este webhook no retransmite estados de transición (processing, fallback, fallback_processing) ni el estado inicial initialized. Solo se entrega en eventos relevantes:
- Estado terminal — la pista alcanza
ready,incomplete,erroroinit_error. - Archivos completos — se sella
track.files_completed_atcuando se ha intentado cada variante (incluyendo las opcionales y las posteriores). Puede llegar después de unreadyanterior, así que la misma pista puede entregarse dos veces: una cuando se vuelve reproducible y otra cuando el conjunto completo de archivos está listo.
Una pista típica se entrega una vez (el estado se asienta en ready y los archivos se completan en el mismo paso) o dos veces (ready temprano por las variantes requeridas, y luego un evento de archivos completos cuando terminan las variantes opcionales).
- Se envía como un HTTP
POSTconContent-Type: application/json. - Incluye una cabecera
X-ADN-Event: track.status_changed. - La entrega es fire-and-forget / al menos una vez — diseña tu manejador para que sea idempotente.
- No se garantiza el orden de eventos; usa los campos
status,previous_statusytrack.files_completed_aten lugar de asumir el orden de llegada. - Las solicitudes actualmente no están firmadas. Trata la URL del webhook como un secreto y, si hace falta, verifica el
track_idcontra la API. - Responde con prontitud con un código de estado
2xxpara acusar recibo.
Estados de pista
El campo status refleja el track_status_id de la pista en el momento del evento. Los valores posibles son:
| Estado | Terminal | Significado |
|---|---|---|
| initialized | No | Se creó el registro de la pista y está esperando a que termine de subirse su archivo. Este es el primer evento que recibes. |
| processing | No | La subida se completó y AudioDN está transcodificando, generando formas de onda, extrayendo metadatos y construyendo variantes. |
| fallback | No | El procesador principal no pudo manejar el archivo, así que se ha encolado para el procesador de respaldo. |
| fallback_processing | No | El procesador de respaldo está trabajando activamente en la pista. |
| ready | Sí | El procesamiento terminó con éxito. Todos los track files están disponibles y la pista está lista para reproducción. |
| incomplete | Sí | El procesamiento terminó con una pista reproducible, pero una o más variantes no esenciales fallaron. La pista es usable. |
| error | Sí | El procesamiento falló y no se pudo producir una pista reproducible. |
| init_error | Sí | No se pudo inicializar la pista (por ejemplo, la subida no era válida o no se podía leer). No hubo procesamiento. |
Los estados terminales (ready, incomplete, error, init_error) son estados finales — una pista no volverá a cambiar de estado después de alcanzar uno. Los demás estados son de transición.
Esquema del payload
Cada evento entrega el mismo sobre. Como las entregas se limitan a resultados terminales y al evento de archivos completos, el array files normalmente está poblado. track.files_completed_at es null en un ready temprano (variantes opcionales aún en curso) y una marca de tiempo cuando el conjunto completo está listo. Cada archivo incluye is_success y un objeto anidado variant (receta); una variante fallida aparece con is_success: false, sin url y el motivo en 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"
},
"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"
}
}
}
]
} Imágenes de portada
Cuando una pista tiene arte de portada, track.cover_image proporciona URLs listas para usar en cada tamaño disponible, y track.cloudflare_image_id almacena el ID de imagen subyacente si necesitas reconstruir una URL más tarde. Ambos son null hasta que hay una portada disponible (las portadas se extraen durante el procesamiento, así que eventos tempranos como initialized pueden no incluir una aún).
Prefiere las URLs cover_image ya preparadas. Si solo tienes el ID de imagen de un evento anterior, puedes construir cualquier tamaño tú mismo. Cada URL sigue el patrón de abajo, donde <variant> es uno de los tamaños con nombre:
https://imagedelivery.net/iVHDyjXAr_lt5UUJaLbk1Q/{cloudflare_image_id}/{variant} | Variante | Dimensiones | URL de ejemplo |
|---|---|---|
| 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 |
El mismo esquema se aplica al arte de portada de colección entregado por el webhook Collection Sync.
Cuando se procesa una portada, ADN también extrae una paleta de colores de ella, entregada como track.theme — un array de objetos de color con valores hex, area, lightness y saturation (null hasta que se haya procesado una portada). Es el mismo campo theme que devuelve la Tracks API.
Ejemplos de payload
Ready temprano (variantes requeridas listas)
Se envía en cuanto las variantes requeridas tienen éxito y la pista es reproducible, mientras las variantes opcionales siguen procesándose. files_completed_at es null porque el conjunto completo de archivos aún no está listo. Una pista sin variantes opcionales pasa directamente al evento asentado de abajo y nunca envía este.
{
"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 y archivos completos (asentado)
Se envía cuando se ha intentado cada variante (requerida y opcional). Se sella files_completed_at y el conjunto completo de archivos está presente. Para una pista sin variantes opcionales, esta es la única entrega que recibes.
{
"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 (éxito parcial)
Se envía cuando se produjo una pista reproducible pero falló una variante no esencial. La pista sigue siendo 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 (fallo)
Se envía cuando el procesamiento falla y no se pudo producir una pista reproducible. El array files está vacío.
{
"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": []
} Verificar la entrega
Cada intento de entrega se registra en el lado de AudioDN, incluyendo el código de estado HTTP devuelto por tu endpoint. Si dejas de recibir eventos, confirma que tu endpoint responde con 2xx rápidamente y que la URL del webhook en Settings es correcta.
Manejar webhooks
Un manejador mínimo lee el campo status y reacciona a los estados terminales. Como la entrega es al menos una vez y sin orden garantizado, basa tu lógica en track_id + status en lugar del orden de recepción.
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.
})