Tài liệu API XuniBOX

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.

Webhook real-time

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.

GET/webhooks

Danh sách webhook của bạn kèm tình trạng giao vận gần nhất.

POST/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ự.
DELETE/webhooks?id=…

Gỡ một webhook theo id.

Bước 1 — Đăng ký

Request
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"
  }'
Response
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.
secret chỉ hiện một lần. Nó không xuất hiện lại ở GET /webhooks. Nếu làm mất, hãy xoá webhook và đăng ký lại để lấy secret mới.

Bước 2 — Hiểu gói tin

Ví dụ POST gửi tới URL của bạn
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-EventTên sự kiện, trùng với trường event trong body.
X-XuniBOX-TimestampThời điểm gửi theo ISO 8601 UTC. Dùng để chống replay.
X-XuniBOX-Signaturesha256=<hex>. HMAC-SHA256 của chuỗi `${timestamp}.${rawBody}` với secret của webhook.
User-AgentXuniBOX-Webhook/1.0.
Sự kiệnKhi nào bắnViệc bạn nên làm
track.createdAdmin đă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.deletedBà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.

Bước 3 — Xác minh chữ ký

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
// 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 / Laravel
<?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';
Phải dùng raw body. Nếu framework của bạn parse JSON rồi serialize lại, khoảng trắng và thứ tự khoá sẽ khác đi và chữ ký sẽ luôn sai.

Bước 4 — Quản lý và theo dõi

Danh sách & xoá webhook
# 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ậnChi tiết
Timeout10 giây. Hãy trả 2xx ngay rồi xử lý nền bằng hàng đợi.
RetryThử 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ặpGiao 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 đa5 webhook cho mỗi tài khoản đối tác.
Không muốn dựng endpoint public? Bạn vẫn có thể đồng bộ bằng cách poll /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.

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

Gửi thông tin sản phẩm để admin XuniBOX xét duyệt và cấp API key.

Xin quyền tích hợp