GET
/webhooksDanh sách webhook của bạn kèm tình trạng giao vận gần nhất.
API chỉ đọc để lấy danh sách nhạc, nghệ sĩ và thể loại từ kho XuniBOX. Mỗi chủ đề dưới đây là một trang riêng, có đường dẫn riêng để bạn chia sẻ cho đội kỹ thuật.
Thay vì poll /tracks?sort=newest mỗi vài phút, bạn đăng ký một URL để XuniBOX chủ động gọi sang mỗi khi kho nhạc thay đổi. Mỗi lần gửi đều được ký HMAC-SHA256 nên bạn xác minh được gói tin thật sự đến từ XuniBOX.
/webhooksDanh sách webhook của bạn kèm tình trạng giao vận gần nhất.
/webhooksĐăng ký webhook mới. Tối đa 5 webhook cho mỗi tài khoản đối tác.
urlstringBắt buộc HTTPS. Đây là nơi XuniBOX gửi POST khi có sự kiện.eventsstring[]track.created, track.updated, track.deleted. Bỏ trống là đăng ký cả ba.descriptionstringGhi chú nội bộ, tối đa 200 ký tự./webhooks?id=…Gỡ một webhook theo id.
curl --request POST \
--url 'https://xunibox.vn/api/public/webhooks' \
--header 'X-API-Key: xnb_live_your_key' \
--header 'Content-Type: application/json' \
--data '{
"url": "https://your-app.com/hooks/xunibox",
"events": ["track.created", "track.updated", "track.deleted"],
"description": "Đồng bộ kho nhạc production"
}'HTTP/1.1 201 Created
{
"data": {
"id": "0f9a1c22-7d3e-4b6a-9e11-2b0c4d5e6f70",
"url": "https://your-app.com/hooks/xunibox",
"events": ["track.created", "track.updated", "track.deleted"],
"is_active": true,
"secret": "whsec_9f2c8ab1...e40",
"created_at": "2026-08-06T05:12:00.000Z"
},
"meta": { "signature_header": "X-XuniBOX-Signature" }
}
// secret CHỈ hiện đúng một lần ở phản hồi này. Lưu ngay vào biến môi trường.
// Tối đa 5 webhook cho mỗi tài khoản đối tác.GET /webhooks. Nếu làm mất, hãy xoá webhook và đăng ký lại để lấy secret mới.POST /hooks/xunibox HTTP/1.1
Content-Type: application/json
X-XuniBOX-Event: track.created
X-XuniBOX-Timestamp: 2026-08-06T05:12:41.882Z
X-XuniBOX-Signature: sha256=6f3d9c1e...b72a
User-Agent: XuniBOX-Webhook/1.0
{
"event": "track.created",
"sent_at": "2026-08-06T05:12:41.882Z",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Hoà Nắng",
"artist": "Hoàng Nam",
"genre": "Indie",
"duration": "3:24"
}
}| Header | Ý nghĩa |
|---|---|
| X-XuniBOX-Event | Tên sự kiện, trùng với trường event trong body. |
| X-XuniBOX-Timestamp | Thời điểm gửi theo ISO 8601 UTC. Dùng để chống replay. |
| X-XuniBOX-Signature | sha256=<hex>. HMAC-SHA256 của chuỗi `${timestamp}.${rawBody}` với secret của webhook. |
| User-Agent | XuniBOX-Webhook/1.0. |
| Sự kiện | Khi nào bắn | Việc bạn nên làm |
|---|---|---|
| track.created | Admin đăng một bài nhạc mới. | Thêm bài vào chỉ mục của bạn, sẵn sàng cho người dùng chọn. |
| track.updated | Đổi tiêu đề, nghệ sĩ, thể loại, ảnh bìa hoặc đoạn preview. | Cập nhật bản ghi tương ứng theo id. Xoá cache cũ của bài đó. |
| track.deleted | Bài bị gỡ khỏi kho. | Ẩn bài khỏi bộ chọn nhạc trong vòng 24 giờ theo điều khoản sử dụng. |
Không bao giờ tin payload trước khi kiểm tra chữ ký. Chuỗi được ký là timestamp + "." + rawBody, so sánh phải dùng hàm timing-safe.
// Node.js / Express — xác minh chữ ký trước khi tin payload.
import crypto from "node:crypto";
import express from "express";
const app = express();
// Cần RAW body để tính HMAC, không được dùng express.json() trước bước này.
app.post("/hooks/xunibox", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.header("X-XuniBOX-Signature") ?? "";
const timestamp = req.header("X-XuniBOX-Timestamp") ?? "";
const rawBody = req.body.toString("utf8");
// Chống replay: bỏ qua gói cũ hơn 5 phút.
if (Math.abs(Date.now() - Date.parse(timestamp)) > 5 * 60 * 1000) {
return res.status(400).send("timestamp quá cũ");
}
// Chuỗi được ký là: "${timestamp}.${rawBody}"
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.XUNIBOX_WEBHOOK_SECRET)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const a = Buffer.from(signature);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send("chữ ký không hợp lệ");
}
const payload = JSON.parse(rawBody);
// Trả 2xx NGAY, xử lý nặng đưa vào hàng đợi nền.
res.status(200).send("ok");
void queue.push(payload);
});<?php
// PHP thuần / Laravel controller
$raw = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_XUNIBOX_SIGNATURE'] ?? '';
$timestamp = $_SERVER['HTTP_X_XUNIBOX_TIMESTAMP'] ?? '';
if (abs(time() - strtotime($timestamp)) > 300) {
http_response_code(400);
exit('timestamp quá cũ');
}
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $raw, getenv('XUNIBOX_WEBHOOK_SECRET'));
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('chữ ký không hợp lệ');
}
$payload = json_decode($raw, true);
switch ($payload['event']) {
case 'track.created':
case 'track.updated':
upsertTrack($payload['data']);
break;
case 'track.deleted':
// Bắt buộc theo điều khoản: ngừng dùng cho nội dung MỚI trong 24 giờ.
disableTrack($payload['data']['id']);
break;
}
http_response_code(200);
echo 'ok';# Xem danh sách webhook đang đăng ký
curl -sS --url 'https://xunibox.vn/api/public/webhooks' \
--header 'X-API-Key: xnb_live_your_key' | jq
# Phản hồi kèm sức khỏe từng webhook:
# {
# "data": [{
# "id": "0f9a...", "url": "https://your-app.com/hooks/xunibox",
# "events": ["track.created","track.updated","track.deleted"],
# "is_active": true,
# "last_delivery_at": "2026-08-06T05:12:42.101Z",
# "last_status_code": 200,
# "consecutive_failures": 0
# }],
# "meta": { "supported_events": [...], "signature_header": "X-XuniBOX-Signature" }
# }
# Xoá một webhook
curl -sS --request DELETE --url 'https://xunibox.vn/api/public/webhooks?id=0f9a1c22-7d3e-4b6a-9e11-2b0c4d5e6f70' \
--header 'X-API-Key: xnb_live_your_key'| Quy tắc giao vận | Chi tiết |
|---|---|
| Timeout | 10 giây. Hãy trả 2xx ngay rồi xử lý nền bằng hàng đợi. |
| Retry | Thử lại theo backoff tăng dần khi bạn trả lỗi 5xx hoặc timeout. |
| consecutive_failures | Đếm số lần hỏng liên tiếp. Webhook hỏng kéo dài sẽ bị tự động tạm ngưng. |
| Trùng lặp | Giao vận theo mô hình at-least-once. Hãy xử lý idempotent theo id bài hát. |
| Thứ tự | Không đảm bảo thứ tự tuyệt đối. So sánh sent_at trước khi ghi đè dữ liệu. |
| Tối đa | 5 webhook cho mỗi tài khoản đối tác. |
/tracks?sort=newest&limit=50 mỗi 15–60 phút rồi đối chiếu id với danh sách đã lưu ở lần đồng bộ trước — chỉ là bạn sẽ biết tin chậm hơn.Gửi thông tin sản phẩm để admin XuniBOX xét duyệt và cấp API key.