Webhooks
Events von HILLS Music an Ihrem HTTPS-Endpunkt: Abonnieren, die 13 Eventtypen, der Umschlag, X-Hills-Signature prüfen, Wiederholungen und Protokoll.
Aktualisiert
Webhooks melden Ihrem System, wenn bei HILLS Music etwas passiert, sodass Sie nicht pollen müssen. HILLS sendet für jedes abonnierte Event einen signierten POST an Ihren HTTPS-Endpunkt. Webhooks sind zusammen mit der API Teil der Tarife Enterprise und Custom.
Abonnieren
Legen Sie einen Endpunkt mit POST /webhooks an (Scope webhooks:write). Sie können ihn auch in der HILLS Web-App unter Settings → Webhooks hinzufügen.
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"]}'
Die Antwort (201) enthält den Endpunkt und sein Signing-Secret, whsec_…. Das Secret wird nur einmal angezeigt. Bewahren Sie es zusammen mit Ihren anderen Secrets auf.
Die URL muss mit https:// beginnen, Port 443 verwenden und öffentlich erreichbar sein. Private, Loopback-, Link-Local- und Cloud-Metadaten-Adressen werden mit 400 invalid_url abgelehnt, sowohl beim Speichern der URL als auch bei jeder Anfrage. Sie können bis zu 20 Endpunkte haben (danach 409 too_many).
Alle Anfragen für Webhook-Endpunkte mit ihren Feldern und Fehlern finden Sie in der Webhooks-Referenz. GET /webhooks/events listet die Eventtypen mit ihrer Bedeutung auf.
Events
| Event | Gesendet, wenn |
|---|---|
release.submitted |
Ein Release wurde zur Prüfung eingereicht. |
release.approved |
Ein Release wurde freigegeben und ist für seine Stores eingereiht. |
release.changes_requested |
Ein Release wurde mit den nötigen Korrekturen zurückgegeben. |
release.live |
Ein Release ist in seinem ersten Store live gegangen. |
delivery.store_status_changed |
Der Status eines Releases in einem Store hat sich geändert: unterwegs, gesendet, live, Korrektur durch Sie nötig, Takedown angefordert oder entfernt. |
takedown.completed |
Alle Stores eines freigegebenen Takedown-Antrags haben das Release entfernt. |
ddex.import.report_ready |
Ein DDEX-Test ist abgeschlossen (der Ordner /test, die Web-App oder POST /ddex/validate); sein Bericht liegt vor. |
ddex.import.imported |
Ein an Production gelieferter DDEX-Batch ist fertig importiert: done, partial oder failed. |
statement.published |
Die Abrechnung eines Monats liegt vor (bei White-Label zuerst zu Ihrer Prüfung). |
payout.status_changed |
Eine Auszahlungsanfrage wurde freigegeben, versendet, bezahlt, ist fehlgeschlagen oder wurde storniert. |
request.status_changed |
HILLS Music hat eine Anfrage beantwortet: freigegeben, Änderungen nötig, abgeschlossen oder abgelehnt. |
chat.message |
HILLS Music hat Ihnen im Live-Support geschrieben. |
webhook.test |
Wird nur von POST /webhooks/{id}/test gesendet. Kann nicht abonniert werden. |
Der Umschlag
Jedes Event hat dieselbe äußere Struktur:
{
"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" } }
}
| Feld | Bedeutung |
|---|---|
id |
Die UUID des Events. Bei jedem Versuch gleich: Deduplizieren Sie anhand dieses Werts. |
type |
Der Eventtyp. |
api_version |
v1. Innerhalb einer Version kommen Felder nur hinzu. |
occurred_at |
Wann es passiert ist, ISO 8601 in UTC. |
account_id |
Ihr Kundenkonto. |
data |
Die Daten des Events. Ihre Struktur hängt von type ab. |
Jede Anfrage enthält außerdem X-Hills-Signature, X-Hills-Event (den Typ), X-Hills-Delivery (die Zustellungs-ID, bei jedem Versuch gleich), X-Hills-Attempt (1, 2, 3 …), Content-Type: application/json und User-Agent: HILLS-Webhooks/1.0.
Ein Body ist höchstens 256 KB groß. Ein größeres Event wird mit data.truncated: true und nur mit seinen IDs gesendet. Den Rest lesen Sie über die API.
Die Reihenfolge der Events ist nicht garantiert. Verwenden Sie occurred_at und lesen Sie den aktuellen Zustand über die API, wenn es auf die Reihenfolge ankommt.
Die Signatur prüfen
X-Hills-Signature sieht so aus: t=1792141500,v1=5257a869…. So prüfen Sie sie:
- Lesen Sie
t(Unix-Sekunden) undv1aus dem Header. - Lehnen Sie die Anfrage ab, wenn
tmehr als 5 Minuten von Ihrer Uhrzeit abweicht. Das verhindert Replay-Angriffe. - Berechnen Sie HMAC-SHA256 über
<t>.<raw body>, mit Ihrem Secret unverändert als Schlüssel (einschließlichwhsec_). Codieren Sie das Ergebnis hexadezimal. - Vergleichen Sie es in konstanter Zeit mit
v1.
Verwenden Sie die unveränderten Bytes des Bodys. Geparstes und neu serialisiertes JSON stimmt nicht überein.
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);
Schnell antworten
Jeder 2xx innerhalb von 10 Sekunden gilt als Erfolg. Alles andere gilt als Fehlschlag, auch ein Timeout oder ein 3xx (Weiterleitungen werden nicht verfolgt). Antworten Sie zuerst und erledigen Sie die Arbeit dann über eine Warteschlange.
Wiederholungen
Eine fehlgeschlagene Zustellung wird erneut versucht, insgesamt bis zu 8-mal. Nach jedem Fehlschlag folgt der nächste Versuch 1 Minute, 5 Minuten, 30 Minuten, 2 Stunden, 12 Stunden, 12 Stunden und 12 Stunden später: insgesamt etwa anderthalb Tage. Nach dem 8. Fehlschlag wird die Zustellung aufgegeben, Sie können sie aber weiterhin von Hand wiederholen.
Automatische Deaktivierung
Ein Endpunkt, bei dem 20 Versuche in Folge ohne zwischenzeitlichen Erfolg fehlschlagen, wird deaktiviert. Sie erhalten eine Benachrichtigung. Zustellungen, die zu diesem Zeitpunkt noch warten, werden aufgegeben.
GET /webhooks/{id} zeigt den Grund: enabled: false, disabled_at und disabled_reason. Beheben Sie das Problem an Ihrem Endpunkt und schalten Sie ihn dann wieder ein:
curl -X PATCH "https://api.hillsmusic.com/v1/webhooks/7" \
-H "Authorization: Bearer $HILLS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled":true}'
Dadurch wird failure_count zurückgesetzt. Wiederholen Sie anschließend, was aufgegeben wurde.
Das Zustellprotokoll
GET /webhooks/{id}/deliveries listet auf, was in den letzten 30 Tagen gesendet wurde, das Neueste zuerst: Payload, Versuche, letzter HTTP-Status, Zeitpunkt und Fehler. Filtern Sie mit status (pending, succeeded, failed) und event. Die Paginierung erfolgt mit before. Siehe Paginierung.
So senden Sie eine Zustellung mit derselben Event-ID und Payload erneut:
curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/deliveries/1042/retry" \
-H "Authorization: Bearer $HILLS_API_KEY"
Wiederholung und Test antworten mit 409 endpoint_disabled, solange der Endpunkt deaktiviert ist.
Das Secret erneuern
POST /webhooks/{id}/roll-secret erzeugt ein neues Secret. Die Antwort enthält es einmalig. Das alte Secret ist sofort ungültig, planen Sie den Wechsel also:
- Lassen Sie Ihre Prüfung eine Liste von Secrets akzeptieren und jedes nacheinander ausprobieren.
- Erneuern Sie das Secret und fügen Sie das neue der Liste hinzu.
- Sobald Zustellungen mit dem neuen Secret die Prüfung bestehen, entfernen Sie das alte.
Erneuern Sie das Secret sofort, wenn es offengelegt wurde.
Test-Event senden
POST /webhooks/{id}/test reiht ein webhook.test-Event für den Endpunkt ein, unabhängig davon, welche Events er abonniert hat. Es wird wie jedes andere Event signiert, und sein Ergebnis erscheint im Zustellprotokoll.
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
Zum Testen auf Ihrem eigenen Rechner verwenden Sie einen Tunnel, der Ihnen eine öffentliche HTTPS-URL bereitstellt.