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:
- Đọc
t(giây Unix) vàv1từ header. - Từ chối request nếu
tlệ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). - 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. - So sánh với
v1bằ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:
- 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.
- Đổi secret và thêm secret mới vào danh sách.
- 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.