Webhooks
Recevez les événements HILLS Music sur votre endpoint HTTPS : abonnement, 13 types d’événements, enveloppe, signature, relances et journal.
Mis à jour le
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.
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. 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 :
{
"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 :
- Lisez
t(secondes Unix) etv1dans l’en-tête. - Refusez la requête si
ts’écarte de plus de 5 minutes de votre horloge. Cela bloque les rejeux. - Calculez le HMAC-SHA256 de
<t>.<raw body>, avec votre secret tel quel comme clé (préfixewhsec_compris). Encodez-le en hexadécimal. - Comparez-le à
v1en temps constant.
Utilisez les octets bruts du corps. Un JSON analysé puis resérialisé ne correspondra pas.
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.
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 :
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.
Pour renvoyer une livraison, avec le même identifiant d’événement et le même payload :
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.
- Faites en sorte que votre vérificateur accepte une liste de secrets et les essaie l’un après l’autre.
- Renouvelez le secret et ajoutez le nouveau à la liste.
- 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.
curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/test" \
-H "Authorization: Bearer $HILLS_API_KEY"
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.