Webhook Track Processing
Le webhook Track Processing permet à votre application de réagir au résultat d’une piste sans interroger. AudioDN envoie une requête HTTP POST à l’URL de webhook configurée pour votre organisation lorsqu’une piste atteint un statut terminal ou lorsque son jeu de fichiers complet est prêt. Pour le progrès par fichier à mesure que chaque variante est produite, utilisez plutôt le webhook Track File.
Configuré dans le tableau de bord
L’URL du webhook est définie dans votre panneau Settings → Webhook du tableau de bord, pas via l’API. Laissez-la vide pour désactiver les webhooks. Pour les événements par fichier, voir le webhook Track File, et pour les événements de création/mise à jour/suppression de collection, voir le webhook Collection Sync.
Quand il se déclenche
Pour éviter le bruit, ce webhook ne relaie pas les statuts transitoires (processing, fallback, fallback_processing) ni l’état initial initialized. Il n’est livré que pour les événements notables :
- Statut terminal — la piste atteint
ready,incomplete,errorouinit_error. - Fichiers complets —
track.files_completed_atest horodaté une fois que chaque variante (y compris les optionnelles et celles après coup) a été tentée. Cela peut arriver après unreadyplus tôt, donc la même piste peut être livrée deux fois : une fois lorsqu’elle devient lisible, et une fois lorsque le jeu de fichiers complet est terminé.
Une piste typique est livrée une fois (le statut se fixe à ready et les fichiers se terminent dans la même étape) ou deux fois (ready anticipé des variantes requises, puis un événement fichiers-complets plus tard lorsque les variantes optionnelles terminent).
- Envoyé comme un HTTP
POSTavecContent-Type: application/json. - Inclut un en-tête
X-ADN-Event: track.status_changed. - La livraison est fire-and-forget / au moins une fois — concevez votre gestionnaire pour être idempotent.
- L’ordre des événements n’est pas garanti ; utilisez les champs
status,previous_statusettrack.files_completed_atplutôt que d’assumer l’ordre d’arrivée. - Les requêtes sont actuellement non signées. Traitez l’URL du webhook comme un secret et, si besoin, vérifiez le
track_idauprès de l’API. - Répondez rapidement avec un code de statut
2xxpour accuser réception.
Statuts de piste
Le champ status reflète le track_status_id de la piste au moment de l’événement. Les valeurs possibles sont :
| Statut | Terminal | Signification |
|---|---|---|
| initialized | Non | L'enregistrement de la piste a été créé et attend que son fichier termine l'envoi. C'est le premier événement que vous recevez. |
| processing | Non | L'envoi est terminé et AudioDN effectue le transcodage, génère des formes d'onde, extrait les métadonnées et construit les variantes. |
| fallback | Non | Le processeur principal n'a pas pu gérer le fichier, il a donc été mis en file pour le processeur de secours. |
| fallback_processing | Non | Le processeur de secours travaille activement sur la piste. |
| ready | Oui | Le traitement s'est terminé avec succès. Tous les fichiers de piste sont disponibles et la piste est prête pour la lecture. |
| incomplete | Oui | Le traitement s'est terminé avec une piste lisible, mais une ou plusieurs variantes non essentielles ont échoué. La piste est utilisable. |
| error | Oui | Le traitement a échoué et aucune piste lisible n'a pu être produite. |
| init_error | Oui | La piste n'a pas pu être initialisée (par exemple, l'envoi était invalide ou illisible). Aucun traitement n'a eu lieu. |
Les statuts terminaux (ready, incomplete, error, init_error) sont des états finaux — une piste ne changera plus de statut après en avoir atteint un. Les autres statuts sont transitoires.
Schéma du payload
Chaque événement livre la même enveloppe. Parce que les livraisons sont limitées aux résultats terminaux et à l’événement fichiers-complets, le tableau files est normalement rempli. track.files_completed_at est null sur un ready anticipé (variantes optionnelles encore en cours) et un horodatage une fois le jeu complet terminé. Chaque fichier inclut is_success et un objet variant imbriqué (recette) ; une variante échouée apparaît avec is_success: false, pas d’url, et la raison dans 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"
}
}
}
]
} Images de couverture
Lorsqu’une piste a une pochette, track.cover_image fournit des URL prêtes à l’emploi à chaque taille disponible, et track.cloudflare_image_id stocke l’identifiant d’image sous-jacent si vous devez reconstruire une URL plus tard. Les deux sont null jusqu’à ce qu’une couverture soit disponible (les couvertures sont extraites pendant le traitement, donc les événements précoces comme initialized peuvent ne pas encore en inclure).
Préférez les URL cover_image toutes faites. Si vous n’avez que l’identifiant d’image d’un événement précédent, vous pouvez construire n’importe quelle taille vous-même. Chaque URL suit le modèle ci-dessous, où <variant> est l’une des tailles nommées :
https://imagedelivery.net/iVHDyjXAr_lt5UUJaLbk1Q/{cloudflare_image_id}/{variant} | Variante | Dimensions | URL d’exemple |
|---|---|---|
| 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 |
Le même schéma s’applique à la pochette de collection livrée par le webhook Collection Sync.
Lorsqu’une couverture est traitée, ADN extrait aussi une palette de couleurs, livrée comme track.theme — un tableau d’objets couleur avec les valeurs hex, area, lightness et saturation (null jusqu’à ce qu’une couverture ait été traitée). C’est le même champ theme renvoyé par l’API Tracks.
Exemples de payloads
Ready anticipé (variantes requises terminées)
Envoyé dès que les variantes requises réussissent et que la piste est lisible, tandis que les variantes optionnelles sont encore en traitement. files_completed_at est null car le jeu de fichiers complet n’est pas encore terminé. Une piste sans variantes optionnelles passe directement à l’événement finalisé ci-dessous et n’envoie jamais celui-ci.
{
"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 et fichiers complets (finalisé)
Envoyé une fois que chaque variante (requise et optionnelle) a été tentée. files_completed_at est horodaté et le jeu de fichiers complet est présent. Pour une piste sans variantes optionnelles, c’est la seule livraison que vous recevez.
{
"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 (succès partiel)
Envoyé lorsqu’une piste lisible a été produite mais qu’une variante non essentielle a échoué. La piste est toujours utilisable.
{
"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 (échec)
Envoyé lorsque le traitement échoue et qu’aucune piste lisible n’a pu être produite. Le tableau files est vide.
{
"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": []
} Vérification de la livraison
Chaque tentative de livraison est journalisée côté AudioDN, y compris le code de statut HTTP renvoyé par votre point de terminaison. Si vous cessez de recevoir des événements, confirmez que votre point de terminaison renvoie rapidement une réponse 2xx et que l’URL du webhook dans Settings est correcte.
Gestion des webhooks
Un gestionnaire minimal lit le champ status et réagit aux états terminaux. Parce que la livraison est au moins une fois et non ordonnée, cléz votre logique sur track_id + status plutôt que sur l’ordre de réception.
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.
})