# 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ý. 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**. ```bash 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](https://developer.hillsmusic.com/vi/reference/webhooks/). `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: ```json { "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 `.`, 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. ```js 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](#the-delivery-log). ## 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: ```bash 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](https://developer.hillsmusic.com/vi/guides/pagination/). Để gửi lại một lần gửi, với cùng id sự kiện và payload: ```bash 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. ```bash curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/test" \ -H "Authorization: Bearer $HILLS_API_KEY" ``` ```js 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. Nguồn: https://developer.hillsmusic.com/vi/guides/webhooks/