# Webhooks > Recevez les événements HILLS Music sur votre endpoint HTTPS : abonnement, 13 types d’événements, enveloppe, signature, relances et journal. Les webhooks préviennent votre système quand quelque chose se produit dans HILLS Music : plus besoin d’interroger l’API en boucle. HILLS envoie un `POST` signé à votre endpoint HTTPS pour chaque événement auquel vous êtes abonné. Les webhooks sont fournis avec l’API dans les offres Enterprise et Custom. ## S’abonner Créez un endpoint avec `POST /webhooks` (scope `webhooks:write`). Vous pouvez aussi en ajouter un dans l’application web HILLS, sous **Settings → Webhooks**. ```bash curl -X POST "https://api.hillsmusic.com/v1/webhooks" \ -H "Authorization: Bearer $HILLS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/hills/webhooks","description":"Order system","events":["release.approved","delivery.store_status_changed"]}' ``` La réponse (`201`) contient l’endpoint et son secret de signature, `whsec_…`. **Le secret n’est affiché qu’une fois.** Stockez-le avec vos autres secrets. L’URL doit être en `https://` sur le port 443, à une adresse publique. Les adresses privées, de bouclage, link-local et de métadonnées cloud sont refusées avec `400 invalid_url`, à l’enregistrement de l’URL comme à chaque requête. Vous pouvez avoir jusqu’à 20 endpoints (au-delà, `409 too_many`). Toutes les requêtes liées aux endpoints webhook, avec leurs champs et leurs erreurs, figurent dans la [référence Webhooks](https://developer.hillsmusic.com/fr/reference/webhooks/). `GET /webhooks/events` liste les types d’événements avec la signification de chacun. ## Événements | Événement | Envoyé quand | | --- | --- | | `release.submitted` | Une sortie a été soumise à vérification. | | `release.approved` | Une sortie a été approuvée et attend d’être envoyée à ses plateformes. | | `release.changes_requested` | Une sortie a été renvoyée avec les corrections à apporter. | | `release.live` | Une sortie est passée en ligne sur sa première plateforme. | | `delivery.store_status_changed` | Le statut d’une sortie sur une plateforme a changé : en cours d’envoi, envoyée, en ligne, correction requise, retrait demandé ou retirée. | | `takedown.completed` | Toutes les plateformes d’une demande de retrait approuvée ont retiré la sortie. | | `ddex.import.report_ready` | Un test DDEX est terminé (dossier `/test`, application web ou `POST /ddex/validate`) ; son rapport est prêt. | | `ddex.import.imported` | Un lot DDEX livré en production a fini d’être importé : done, partial ou failed. | | `statement.published` | Le relevé de revenus d’un mois est prêt (en marque blanche, il vous est d’abord soumis pour vérification). | | `payout.status_changed` | Une demande de versement a été approuvée, envoyée, payée, a échoué ou a été annulée. | | `request.status_changed` | HILLS Music a répondu à une demande : approuvée, modifications nécessaires, terminée ou refusée. | | `chat.message` | HILLS Music vous a écrit dans le support en direct. | | `webhook.test` | Envoyé uniquement par `POST /webhooks/{id}/test`. Vous ne pouvez pas vous y abonner. | ## L’enveloppe Chaque événement a la même structure externe : ```json { "id": "5d0c2b7e-8a43-4c4e-9f1e-2f0b9a6c1d33", "type": "release.approved", "api_version": "v1", "occurred_at": "2026-10-13T09:05:00Z", "account_id": 12, "data": { "release": { "id": 812, "title": "Midnight Run" } } } ``` | Champ | Signification | | --- | --- | | `id` | L’UUID de l’événement. Identique à chaque tentative : dédoublonnez sur cette valeur. | | `type` | Le type d’événement. | | `api_version` | `v1`. Au sein d’une version, des champs peuvent seulement être ajoutés. | | `occurred_at` | Le moment de l’événement, en ISO 8601 et en UTC. | | `account_id` | Votre compte client. | | `data` | Les données de l’événement. Leur forme dépend de `type`. | Chaque requête porte aussi `X-Hills-Signature`, `X-Hills-Event` (le type), `X-Hills-Delivery` (l’identifiant de la livraison, identique à chaque tentative), `X-Hills-Attempt` (1, 2, 3…), `Content-Type: application/json` et `User-Agent: HILLS-Webhooks/1.0`. Un corps fait au plus 256 Ko. Un événement plus volumineux est envoyé avec `data.truncated: true` et seulement ses identifiants. Lisez le reste via l’API. L’ordre d’arrivée des événements n’est pas garanti. Fiez-vous à `occurred_at`, et lisez l’état actuel via l’API quand l’ordre compte. ## Vérifier la signature `X-Hills-Signature` se présente sous la forme `t=1792141500,v1=5257a869…`. Pour la vérifier : 1. Lisez `t` (secondes Unix) et `v1` dans l’en-tête. 2. Refusez la requête si `t` s’écarte de plus de 5 minutes de votre horloge. Cela bloque les rejeux. 3. Calculez le HMAC-SHA256 de `.`, avec votre secret tel quel comme clé (préfixe `whsec_` compris). Encodez-le en hexadécimal. 4. Comparez-le à `v1` en temps constant. Utilisez les octets **bruts** du corps. Un JSON analysé puis resérialisé ne correspondra pas. ```js import crypto from "node:crypto"; import express from "express"; const SECRET = process.env.HILLS_WEBHOOK_SECRET; // whsec_… const app = express(); app.post("/hills/webhooks", express.raw({ type: "application/json" }), (req, res) => { const header = req.get("X-Hills-Signature") ?? ""; const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=", 2))); const t = Number(parts.t); if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(400); const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${req.body}`).digest("hex"); const a = Buffer.from(expected, "hex"); const b = Buffer.from(parts.v1 ?? "", "hex"); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(400); const event = JSON.parse(req.body.toString("utf8")); // Dedupe on event.id: a retry after a lost answer sends the same event again. queue.push(event); // do the work later res.sendStatus(200); // answer within 10 seconds }); app.listen(3000); ``` ## Répondre vite Toute réponse `2xx` en moins de 10 secondes est un succès. Tout le reste est un échec, y compris un délai dépassé ou une `3xx` (les redirections ne sont pas suivies). Répondez d’abord, puis traitez l’événement dans une file d’attente. ## Nouvelles tentatives Une livraison qui échoue est retentée, jusqu’à 8 tentatives au total. Après chaque échec, la tentative suivante a lieu **1 minute, 5 minutes, 30 minutes, 2 heures, 12 heures, 12 heures et 12 heures** plus tard : environ un jour et demi en tout. Après le 8ᵉ échec, la livraison est abandonnée, mais vous pouvez toujours [la relancer manuellement](#the-delivery-log). ## Désactivation automatique Un endpoint qui échoue 20 tentatives d’affilée, sans aucun succès entre-temps, est désactivé. Vous recevez une notification. Les livraisons en attente à ce moment-là sont abandonnées. `GET /webhooks/{id}` en indique la raison : `enabled: false`, `disabled_at` et `disabled_reason`. Corrigez votre endpoint, puis réactivez-le : ```bash curl -X PATCH "https://api.hillsmusic.com/v1/webhooks/7" \ -H "Authorization: Bearer $HILLS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"enabled":true}' ``` Cela remet `failure_count` à zéro. Relancez ensuite ce qui a été abandonné. ## Le journal des livraisons `GET /webhooks/{id}/deliveries` liste ce qui a été envoyé au cours des 30 derniers jours, du plus récent au plus ancien : le payload, les tentatives, le dernier statut HTTP, l’heure et l’erreur. Filtrez avec `status` (`pending`, `succeeded`, `failed`) et `event`. La pagination se fait avec `before`. Voir [Pagination](https://developer.hillsmusic.com/fr/guides/pagination/). Pour renvoyer une livraison, avec le même identifiant d’événement et le même payload : ```bash curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/deliveries/1042/retry" \ -H "Authorization: Bearer $HILLS_API_KEY" ``` La relance et le test renvoient `409 endpoint_disabled` tant que l’endpoint est désactivé. ## Renouveler le secret `POST /webhooks/{id}/roll-secret` génère un nouveau secret. La réponse le contient, une seule fois. **L’ancien secret cesse immédiatement de fonctionner** : préparez la transition. 1. Faites en sorte que votre vérificateur accepte une liste de secrets et les essaie l’un après l’autre. 2. Renouvelez le secret et ajoutez le nouveau à la liste. 3. Une fois que les livraisons passent avec le nouveau secret, retirez l’ancien. Renouvelez-le sans attendre si le secret a fuité. ## Envoyer un événement de test `POST /webhooks/{id}/test` met en file d’attente un événement `webhook.test` pour l’endpoint, quels que soient les événements auxquels il est abonné. Il est signé comme n’importe quel autre événement, et son résultat apparaît dans le journal des livraisons. ```bash curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/test" \ -H "Authorization: Bearer $HILLS_API_KEY" ``` ```js const res = await fetch("https://api.hillsmusic.com/v1/webhooks/7/test", { method: "POST", headers: { Authorization: `Bearer ${process.env.HILLS_API_KEY}` }, }); const { delivery } = await res.json(); // 202; check GET /webhooks/7/deliveries for the outcome ``` Pour tester sur votre propre machine, utilisez un tunnel qui vous fournit une URL HTTPS publique. Source: https://developer.hillsmusic.com/fr/guides/webhooks/