Chuyển đến nội dung
HILLS API v1

Hướng dẫn

Webhook

Nhận sự kiện HILLS Music tại endpoint HTTPS của bạn: đăng ký, 13 loại sự kiện, envelope, xác minh X-Hills-Signature, cơ chế gửi lại và nhật ký.

Cập nhật

Webhook báo cho hệ thống của bạn khi có điều gì đó xảy ra trong HILLS Music, nên bạn không cần polling. Với mỗi sự kiện bạn đăng ký, HILLS gửi một request POST có chữ ký đến endpoint HTTPS của bạn. Webhook đi kèm API trong các gói Enterprise và Custom.

Đăng ký

Tạo một endpoint bằng POST /webhooks (scope webhooks:write). Bạn cũng có thể thêm endpoint trong ứng dụng web HILLS, tại 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"]}'

Phản hồi (201) chứa endpoint và secret dùng để ký, whsec_…. Secret chỉ hiển thị một lần. Hãy lưu nó cùng các secret khác của bạn.

URL phải là https:// trên cổng 443, tại một địa chỉ công khai. Các địa chỉ private, loopback, link-local và metadata của dịch vụ cloud bị từ chối với 400 invalid_url, cả khi bạn lưu URL lẫn ở mỗi request. Bạn có thể có tối đa 20 endpoint (sau đó là 409 too_many).

Mọi request liên quan đến endpoint webhook, cùng các trường và lỗi, có trong tài liệu tham chiếu Webhook. GET /webhooks/events liệt kê các loại sự kiện và ý nghĩa của từng loại.

Sự kiện

Sự kiện Được gửi khi
release.submitted Một bản phát hành được gửi kiểm duyệt.
release.approved Một bản phát hành được duyệt và đã vào hàng đợi gửi đến các nền tảng.
release.changes_requested Một bản phát hành bị trả lại kèm những điểm cần sửa.
release.live Một bản phát hành lên kệ ở nền tảng đầu tiên.
delivery.store_status_changed Trạng thái của một bản phát hành ở một nền tảng thay đổi: đang gửi, đã gửi, đã lên kệ, cần bạn sửa, đã yêu cầu gỡ hoặc đã gỡ.
takedown.completed Mọi nền tảng trong một yêu cầu gỡ đã được duyệt đều đã gỡ bản phát hành.
ddex.import.report_ready Một lần kiểm thử DDEX đã xong (thư mục /test, ứng dụng web hoặc POST /ddex/validate); báo cáo đã sẵn sàng.
ddex.import.imported Một lô DDEX phân phối lên production đã nhập xong: done, partial hoặc failed.
statement.published Sao kê doanh thu của một tháng đã sẵn sàng (với white-label, trước tiên sẵn sàng để bạn xem xét).
payout.status_changed Một yêu cầu thanh toán được duyệt, đã gửi, đã trả, thất bại hoặc bị hủy.
request.status_changed HILLS Music đã trả lời một yêu cầu: được duyệt, cần chỉnh sửa, đã hoàn tất hoặc bị từ chối.
chat.message HILLS Music đã nhắn cho bạn trong hỗ trợ trực tuyến (Live support).
webhook.test Chỉ được gửi bởi POST /webhooks/{id}/test. Bạn không thể đăng ký sự kiện này.

Envelope

Mọi sự kiện đều có cùng một lớp vỏ bên ngoài:

{
  "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" } }
}
Trường Ý nghĩa
id UUID của sự kiện. Giống nhau ở mọi lần thử: hãy khử trùng lặp theo giá trị này.
type Loại sự kiện.
api_version v1. Trong một phiên bản, trường chỉ được thêm vào, không bị bỏ đi.
occurred_at Thời điểm xảy ra, theo ISO 8601, múi giờ UTC.
account_id Tài khoản khách hàng của bạn.
data Dữ liệu của sự kiện. Cấu trúc phụ thuộc vào type.

Mỗi request còn mang X-Hills-Signature, X-Hills-Event (loại sự kiện), X-Hills-Delivery (id của lần gửi, giống nhau ở mọi lần thử), X-Hills-Attempt (1, 2, 3…), Content-Type: application/json và User-Agent: HILLS-Webhooks/1.0.

Body tối đa 256 KB. Sự kiện lớn hơn được gửi với data.truncated: true và chỉ gồm các id. Hãy đọc phần còn lại từ API.

Sự kiện không được bảo đảm đến đúng thứ tự. Hãy dùng occurred_at, và đọc trạng thái hiện tại từ API khi thứ tự là quan trọng.

Xác minh chữ ký

X-Hills-Signature có dạng t=1792141500,v1=5257a869…. Để kiểm tra:

  1. Đọc t (giây Unix) và v1 từ header.
  2. Từ chối request nếu t lệch quá 5 phút so với đồng hồ của bạn. Điều này chặn tấn công phát lại (replay).
  3. Tính HMAC-SHA256 của <t>.<raw body>, với khóa là secret của bạn giữ nguyên (kể cả whsec_). Mã hóa kết quả dạng hex.
  4. So sánh với v1 bằng phép so sánh thời gian hằng định (constant time).

Hãy dùng các byte thô của body. JSON đã parse rồi serialize lại sẽ không khớp.

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);

Phản hồi nhanh

Bất kỳ mã 2xx nào trong vòng 10 giây đều được tính là thành công. Mọi thứ khác đều là thất bại, kể cả timeout hay 3xx (chuyển hướng không được theo). Hãy phản hồi trước, rồi xử lý công việc trong một hàng đợi.

Gửi lại

Một lần gửi thất bại sẽ được thử lại, tối đa 8 lần thử tổng cộng. Sau mỗi lần thất bại, lần thử tiếp theo diễn ra sau 1 phút, 5 phút, 30 phút, 2 giờ, 12 giờ, 12 giờ và 12 giờ: tổng cộng khoảng một ngày rưỡi. Sau lần thất bại thứ 8, lần gửi bị bỏ, nhưng bạn vẫn có thể gửi lại thủ công.

Tự động tắt

Một endpoint thất bại 20 lần thử liên tiếp, không có lần thành công nào xen giữa, sẽ bị tắt. Bạn nhận được thông báo. Các lần gửi đang chờ vào thời điểm đó bị bỏ.

GET /webhooks/{id} cho biết lý do: enabled: false, disabled_at và disabled_reason. Hãy sửa endpoint của bạn, rồi bật lại:

curl -X PATCH "https://api.hillsmusic.com/v1/webhooks/7" \
  -H "Authorization: Bearer $HILLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true}'

Thao tác này đặt lại failure_count. Sau đó hãy gửi lại những gì đã bị bỏ.

Nhật ký gửi

GET /webhooks/{id}/deliveries liệt kê những gì đã được gửi trong 30 ngày gần nhất, mới nhất trước: payload, các lần thử, mã trạng thái HTTP cuối cùng, thời gian và lỗi. Lọc bằng status (pending, succeeded, failed) và event. Danh sách phân trang bằng before. Xem Phân trang.

Để gửi lại một lần gửi, với cùng id sự kiện và payload:

curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/deliveries/1042/retry" \
  -H "Authorization: Bearer $HILLS_API_KEY"

Gửi lại và gửi test trả về 409 endpoint_disabled khi endpoint đang tắt.

Đổi secret

POST /webhooks/{id}/roll-secret tạo một secret mới. Phản hồi chứa secret đó, chỉ một lần. Secret cũ ngừng hoạt động ngay lập tức, nên hãy lên kế hoạch chuyển đổi:

  1. Cho phép bộ xác minh của bạn nhận một danh sách secret: lần lượt thử từng secret.
  2. Đổi secret và thêm secret mới vào danh sách.
  3. Khi các lần gửi đã xác minh được bằng secret mới, hãy xóa secret cũ.

Hãy đổi secret ngay nếu secret bị lộ.

Gửi sự kiện test

POST /webhooks/{id}/test đưa một sự kiện webhook.test vào hàng đợi gửi đến endpoint, bất kể endpoint đăng ký những sự kiện nào. Sự kiện được ký như mọi sự kiện khác, và kết quả hiển thị trong nhật ký gửi.

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

Để kiểm thử trên máy của bạn, hãy dùng một tunnel cung cấp URL HTTPS công khai.

Sẵn sàng tích hợp?

Đăng ký quyền truy cập API, chúng tôi sẽ phản hồi bạn trong vòng 1–3 ngày làm việc.