# Webhook Track Processing | Docs AudioDN

> Le webhook Track Processing notifie votre application lorsqu

Source: https://audiodeliverynetwork.com/fr/docs/webhooks/track-processing/

---

# 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](/docs/webhooks/track-files).

#### 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](/docs/webhooks/track-files), et pour les événements de création/mise à jour/suppression de collection, voir le [webhook Collection Sync](/docs/webhooks/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`, `error` ou `init_error`.
-   **Fichiers complets** — `track.files_completed_at` est horodaté une fois que chaque variante (y compris les optionnelles et celles après coup) a été tentée. Cela peut arriver après un `ready` plus 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 `POST` avec `Content-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_status` et `track.files_completed_at` plutô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_id` auprès de l’API.
-   Répondez rapidement avec un code de statut `2xx` pour 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](/docs/webhooks/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](/docs/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.
})
```
