Zum Inhalt springen
HILLS API v1 Early Access

Anleitungen

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:

  1. Lesen Sie t (Unix-Sekunden) und v1 aus dem Header.
  2. Lehnen Sie die Anfrage ab, wenn t mehr als 5 Minuten von Ihrer Uhrzeit abweicht. Das verhindert Replay-Angriffe.
  3. Berechnen Sie HMAC-SHA256 über <t>.<raw body>, mit Ihrem Secret unverändert als Schlüssel (einschließlich whsec_). Codieren Sie das Ergebnis hexadezimal.
  4. 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:

  1. Lassen Sie Ihre Prüfung eine Liste von Secrets akzeptieren und jedes nacheinander ausprobieren.
  2. Erneuern Sie das Secret und fügen Sie das neue der Liste hinzu.
  3. 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.

Bereit für die Integration?

Beantragen Sie API-Zugang – wir melden uns innerhalb von 1–3 Werktagen bei Ihnen.