Cẩm nang tích hợp APIAPI Integration Handbook
Mọi thứ bạn cần để kích hoạt license, kiểm tra hiệu lực, phân phối bản cập nhật và tự động hoá cấp license từ hệ thống của mình.Everything you need to activate licenses, validate them, ship updates and automate license issuing from your own systems.
MB License Cloud cung cấp gì
What MB License Cloud provides
MB License Cloud là cổng cấp phép (license gateway) tự lưu trữ. Sản phẩm của bạn (plugin WordPress, ứng dụng, dịch vụ) gọi API để kích hoạt license trên một site/thiết bị, sau đó dùng một activation lease ngắn hạn thay cho license key trong mọi lần gọi tiếp theo.
MB License Cloud is a self-hosted license gateway. Your product (a WordPress plugin, an app, a service) calls the API to activate a license on a site or device, then uses a short-lived activation lease instead of the license key for every later call.
Ba nhóm API
Three API families
mbr_….Resellers issue licenses against quota with a mbr_… Bearer key.
Base URL & định dạng
Base URL & format
- Base URL là domain bạn triển khai, ví dụ
https://license.example.com. Trong bản dev:http://localhost:8080. - Body là JSON (
Content-Type: application/json), tối đa 32 KiB cho runtime API. Runtime API còn chấp nhậnapplication/x-www-form-urlencodedđể tương thích bản cũ. - Thời gian phản hồi dùng RFC 3339; timestamp trong yêu cầu là Unix giây.
- Các alias
/wp-json/mb-license/v2/*chỉ phục vụ giai đoạn chuyển đổi; tích hợp mới hãy dùng/api/v2.
- The base URL is the domain you deploy, e.g.
https://license.example.com. In the dev stack:http://localhost:8080. - Bodies are JSON (
Content-Type: application/json), up to 32 KiB for the runtime API. The runtime API also acceptsapplication/x-www-form-urlencodedfor legacy compatibility. - Response times use RFC 3339; request timestamps are Unix seconds.
- The
/wp-json/mb-license/v2/*aliases exist only for staged migrations; new integrations should use/api/v2.
/api/v2/healthcurl -s https://license.example.com/api/v2/health
# {"ok":true,"service":"mb-license-cloud","version":"1.8.0","time":"…"}Đặc tả máy đọc được nằm ở docs/openapi.yaml trong repository.
A machine-readable spec lives at docs/openapi.yaml in the repository.
Bắt đầu nhanhQuickstart
Từ con số 0 đến lần kích hoạt đầu tiên trong năm bước.From zero to your first activation in five steps.
- Triển khai máy chủ. Làm theo README (
docker compose up -d --build) và đăng nhập/admin/login. - Tạo sản phẩm trong Admin → Products. Ghi lại
slug(ví dụcontent-studio-pro) — đó là giá trịproducttrong API. - Cấp một license trong Admin → Licenses. License key chỉ hiển thị một lần; hãy sao chép ngay.
- Ký và gọi
activatenhư ví dụ bên dưới. Lưulease.tokentrả về — nó thay thế license key. - Gọi
check/heartbeatđịnh kỳ bằng lease token để biết license còn hiệu lực.
- Deploy the server. Follow the README (
docker compose up -d --build) and sign in at/admin/login. - Create a product in Admin → Products. Note its
slug(e.g.content-studio-pro) — it is theproductvalue in the API. - Issue a license in Admin → Licenses. The license key is shown once; copy it right away.
- Sign and call
activateas shown below. Store the returnedlease.token— it replaces the license key. - Call
check/heartbeatperiodically with the lease token to learn whether the license is still valid.
Lần kích hoạt đầu tiên
Your first activation
/api/v2/activate// Node.js 18+
const crypto = require('crypto');
const BASE = 'https://license.example.com';
const licenseKey = 'MB-XXXX-XXXX-XXXX-XXXX'; // as issued
const fields = {
route: 'activate',
product: 'content-studio-pro',
site_url: 'https://example.com', // already normalized, see Signature v2
instance_id: '0fd4c1a2e7b94d6f', // stable random ID of this installation
app_version: '4.2.0',
environment: 'production',
channel: 'stable',
transfer_token: '',
};
const ts = Math.floor(Date.now() / 1000);
const nonce = crypto.randomBytes(16).toString('hex');
const canonical = [
fields.route, fields.product, fields.site_url, fields.instance_id,
fields.app_version, String(ts), nonce,
fields.environment, fields.channel, fields.transfer_token,
].join('\n');
// HMAC key = the license key, upper-cased and stripped to A-Z 0-9 and "-"
const key = licenseKey.toUpperCase().replace(/[^A-Z0-9-]/g, '');
const signature = crypto.createHmac('sha256', key).update(canonical).digest('hex');
const res = await fetch(BASE + '/api/v2/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
license_key: licenseKey,
product: fields.product,
site_url: fields.site_url,
instance_id: fields.instance_id,
app_version: fields.app_version,
environment: fields.environment,
channel: fields.channel,
request_ts: ts,
request_nonce: nonce,
signature_version: 2,
request_signature: signature,
}),
});
const data = await res.json();
console.log(data.valid, data.lease && data.lease.token);
<?php
$base = 'https://license.example.com';
$licenseKey = 'MB-XXXX-XXXX-XXXX-XXXX';
$f = [
'route' => 'activate', 'product' => 'content-studio-pro',
'site_url' => 'https://example.com', 'instance_id' => '0fd4c1a2e7b94d6f',
'app_version' => '4.2.0', 'environment' => 'production',
'channel' => 'stable', 'transfer_token' => '',
];
$ts = time();
$nonce = bin2hex(random_bytes(16));
$canonical = implode("\n", [
$f['route'], $f['product'], $f['site_url'], $f['instance_id'],
$f['app_version'], (string) $ts, $nonce,
$f['environment'], $f['channel'], $f['transfer_token'],
]);
$key = preg_replace('/[^A-Z0-9-]/', '', strtoupper($licenseKey));
$sig = hash_hmac('sha256', $canonical, $key);
$ch = curl_init("$base/api/v2/activate");
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'license_key' => $licenseKey, 'product' => $f['product'],
'site_url' => $f['site_url'], 'instance_id' => $f['instance_id'],
'app_version' => $f['app_version'], 'environment' => $f['environment'],
'channel' => $f['channel'], 'request_ts' => $ts,
'request_nonce' => $nonce, 'signature_version' => 2,
'request_signature' => $sig,
]),
]);
$data = json_decode(curl_exec($ch), true);
var_dump($data['valid'] ?? false, $data['lease']['token'] ?? null);
import hashlib, hmac, json, re, secrets, time, urllib.request
BASE = "https://license.example.com"
license_key = "MB-XXXX-XXXX-XXXX-XXXX"
f = dict(route="activate", product="content-studio-pro",
site_url="https://example.com", instance_id="0fd4c1a2e7b94d6f",
app_version="4.2.0", environment="production",
channel="stable", transfer_token="")
ts = int(time.time())
nonce = secrets.token_hex(16)
canonical = "\n".join([f["route"], f["product"], f["site_url"], f["instance_id"],
f["app_version"], str(ts), nonce,
f["environment"], f["channel"], f["transfer_token"]])
key = re.sub(r"[^A-Z0-9-]", "", license_key.upper())
sig = hmac.new(key.encode(), canonical.encode(), hashlib.sha256).hexdigest()
body = {"license_key": license_key, "product": f["product"],
"site_url": f["site_url"], "instance_id": f["instance_id"],
"app_version": f["app_version"], "environment": f["environment"],
"channel": f["channel"], "request_ts": ts, "request_nonce": nonce,
"signature_version": 2, "request_signature": sig}
req = urllib.request.Request(BASE + "/api/v2/activate",
data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"})
data = json.load(urllib.request.urlopen(req))
print(data["valid"], data["lease"]["token"])
BASE=https://license.example.com
KEY='MB-XXXX-XXXX-XXXX-XXXX'
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
CANON=$(printf 'activate\ncontent-studio-pro\nhttps://example.com\n0fd4c1a2e7b94d6f\n4.2.0\n%s\n%s\nproduction\nstable\n' "$TS" "$NONCE")
SIG=$(printf '%s' "$CANON" | openssl dgst -sha256 -hmac "$KEY" -hex | sed 's/^.* //')
curl -sS "$BASE/api/v2/activate" -H 'Content-Type: application/json' -d "{
\"license_key\":\"$KEY\",\"product\":\"content-studio-pro\",
\"site_url\":\"https://example.com\",\"instance_id\":\"0fd4c1a2e7b94d6f\",
\"app_version\":\"4.2.0\",\"environment\":\"production\",\"channel\":\"stable\",
\"request_ts\":$TS,\"request_nonce\":\"$NONCE\",
\"signature_version\":2,\"request_signature\":\"$SIG\"}"Phản hồi thành công (rút gọn)
Successful response (abridged)
{
"ok": true,
"valid": true,
"status": "active",
"message": "License activated.",
"request_id": "…",
"license": { "plan": "pro", "expires_at": "2027-11-12T00:00:00Z",
"max_activations": 1, "active_activations": 1 },
"product": { "slug": "content-studio-pro", "current_version": "4.3.0",
"update_available": true, "entitlements": { } },
"activation_id": 42,
"lease": { "token": "mbl_…", "expires_at_ts": 1787166400, "grace_until_ts": 1787425600 },
"server_time": "2026-11-12T08:00:00Z"
}Lease token là bí mật dùng để ký các lệnh gọi tiếp theo. Không ghi log, không gửi tới bên thứ ba.
The lease token is the secret used to sign every later call. Never log it or send it to third parties.
Xác thựcAuthentication
Mỗi nhóm API dùng một cơ chế xác thực riêng, phù hợp với nơi nó chạy.Each API family uses its own mechanism, matched to where it runs.
| API | Chạy ở đâu | Runs where | Thông tin xác thực | Credential |
|---|---|---|---|---|
/api/v2/* runtime | Sản phẩm của khách (site, app) | Customer's product (site, app) | License key (chỉ lúc activate / transfer) rồi tới lease token; ký HMAC mỗi yêu cầu | License key (activate / transfer only), then lease token; every request HMAC-signed |
/api/integration/v1/* | Backend của bạn | Your backend | Headers X-MB-Client + X-MB-Secret | |
/api/v2/reseller/* | Backend của đại lý | Reseller's backend | Authorization: Bearer mbr_… |
License key và activation lease
License key and activation lease
License key chỉ đi qua mạng trong lần activate (hoặc transfer/claim). Máy chủ chỉ lưu giá trị băm HMAC, rồi trả về một lease ngẫu nhiên gắn với site đó. Các lệnh check, heartbeat, update, deactivate dùng lease token. Nếu lease bị lộ, chỉ một lần kích hoạt bị ảnh hưởng và có thể thu hồi.
The license key crosses the network only for activate (or transfer/claim). The server stores just an HMAC hash and returns a random lease bound to that site. check, heartbeat, update and deactivate use the lease token. If a lease leaks, only one activation is affected and it can be revoked.
Thời hạn lease & chế độ offline
Lease lifetime & offline grace
Lease có hạn (LEASE_TTL, mặc định 24 giờ) cùng thời gian ân hạn (LEASE_GRACE, mặc định 72 giờ). Phản hồi trả lease.expires_at_ts và lease.grace_until_ts. Khi máy chủ không liên lạc được, sản phẩm có thể tiếp tục chạy tới hết grace_until_ts, rồi phải khoá tính năng trả phí.
A lease expires (LEASE_TTL, default 24 h) and has a grace window (LEASE_GRACE, default 72 h). Responses carry lease.expires_at_ts and lease.grace_until_ts. While the server is unreachable your product may keep running until grace_until_ts, after which paid features must lock.
API client (Integration API)
API client (Integration API)
Tạo trong Admin → API Clients. Mỗi client có scope tối thiểu (product:read, license:read, order:write) và có thể giới hạn theo IP/CIDR. Secret chỉ hiển thị một lần.
Create one in Admin → API Clients. Each client has minimal scopes (product:read, license:read, order:write) and can be restricted by IP/CIDR. The secret is shown once.
Reseller API key
Reseller API key
Đại lý tạo key mbr_… trong Reseller Portal → API keys. Key chỉ hiển thị một lần; giới hạn 120 yêu cầu/phút cho mỗi key.
Resellers create mbr_… keys in the Reseller Portal → API keys. A key is shown once; the limit is 120 requests per minute per key.
Không bao giờ nhúng API client secret hoặc mbr_ key vào mã chạy ở trình duyệt hoặc trong sản phẩm phân phối cho khách.
Never embed an API client secret or an mbr_ key in browser code or in a product you ship to customers.
Chữ ký yêu cầu v2Request Signature v2
Mọi yêu cầu runtime phải được ký HMAC-SHA256. Chữ ký ràng buộc route, sản phẩm, site, bản cài đặt, phiên bản, thời điểm và nonce.Every runtime request must be signed with HMAC-SHA256. The signature binds route, product, site, installation, version, time and nonce.
Chuỗi chuẩn hoá (canonical string)
Canonical string
Nối 10 trường sau bằng ký tự xuống dòng \n (không có xuống dòng ở cuối):
Join these 10 fields with a newline \n (no trailing newline):
| # | Trường | Field | Quy tắc | Rule |
|---|---|---|---|---|
| 1 | route | Chữ thường: activate, check, heartbeat, deactivate, update, transfer_claim | Lower-case: activate, check, heartbeat, deactivate, update, transfer_claim | |
| 2 | product | Slug sản phẩm, chữ thường | Product slug, lower-case | |
| 3 | site_url | Đã chuẩn hoá (xem bên dưới) | Normalized (see below) | |
| 4 | instance_id | Đúng như gửi | Exactly as sent | |
| 5 | app_version | Đúng như gửi | Exactly as sent | |
| 6 | request_ts | Unix giây, dạng số thập phân | Unix seconds, decimal | |
| 7 | request_nonce | Chuỗi ngẫu nhiên, dùng một lần | Random string, single use | |
| 8 | environment | Chữ thường; chuỗi rỗng nếu không gửi | Lower-case; empty string if not sent | |
| 9 | channel | Chữ thường; chuỗi rỗng nếu không gửi | Lower-case; empty string if not sent | |
| 10 | transfer_token | Chỉ dùng cho transfer_claim, còn lại để rỗng | Only for transfer_claim, otherwise empty |
Tất cả trường được cắt khoảng trắng đầu/cuối trước khi nối.
All fields are trimmed of leading and trailing whitespace before joining.
Khoá HMAC & kết quả
HMAC key & output
activatevàtransfer_claim: khoá là license key đã chuẩn hoá — viết hoa và chỉ giữA–Z,0–9,-.check,heartbeat,update,deactivate: khoá là lease token nguyên bản.- Kết quả là chuỗi hex 64 ký tự (chữ thường) gửi trong
request_signature, kèmsignature_version: 2.
activateandtransfer_claim: the key is the normalized license key — upper-cased, keeping onlyA–Z,0–9,-.check,heartbeat,update,deactivate: the key is the lease token, verbatim.- The output is a 64-character lower-case hex string sent as
request_signature, withsignature_version: 2.
Chuẩn hoá site_url
Normalizing site_url
Máy chủ chuẩn hoá site_url trước khi kiểm tra chữ ký, nên bạn phải ký đúng dạng đã chuẩn hoá:
The server normalizes site_url before verifying the signature, so you must sign the normalized form:
- Scheme phải là
httphoặchttps; không có user/password. - Host viết thường, bỏ dấu chấm cuối, bỏ tiền tố
www.. - Giữ nguyên cổng nếu có; bỏ dấu
/ở cuối đường dẫn; bỏ query và fragment.
- Scheme must be
httporhttps; no user info. - Host is lower-cased, loses a trailing dot and a leading
www.. - An explicit port is kept; trailing
/is removed from the path; query and fragment are dropped.
https://WWW.Example.com/ → https://example.com https://shop.example.com:8443/ → https://shop.example.com:8443 https://example.com/blog/ → https://example.com/blog
Thời gian & chống phát lại
Time & replay protection
request_ts phải lệch giờ máy chủ tối đa REQUEST_SKEW (mặc định 5 phút); hãy đồng bộ NTP. Mỗi request_nonce chỉ dùng được một lần trong NONCE_TTL (mặc định 10 phút) cho mỗi (khoá, route); dùng lại sẽ nhận 409 signature_replay. Sinh nonce mới cho mỗi lần gọi, kể cả khi thử lại.
request_ts must be within REQUEST_SKEW of server time (default 5 minutes), so keep clocks on NTP. Each request_nonce can be used once per (key, route) within NONCE_TTL (default 10 minutes); reuse returns 409 signature_replay. Generate a fresh nonce for every call, retries included.
Vector kiểm thử
Test vector
Dùng để kiểm tra bản cài đặt của bạn. Khoá (secret) ở đây là một chuỗi ASCII 32 ký tự.
Use it to check your implementation. The secret here is a 32-character ASCII string.
secret = 01234567890123456789012345678901 canonical = "activate\ndemo\nhttps://example.com\ninst\n1.0.0\n1700000000\nnonce-123\nproduction\nstable\nMBT-test" signature = HMAC_SHA256_hex(secret, canonical)
Các vector chuẩn nằm ở testdata/vectors.json (mục signature) và được kiểm bởi crates/mblc-security/tests/vectors.rs.
Reference vectors live in testdata/vectors.json (signature section) and are asserted by crates/mblc-security/tests/vectors.rs.
Các endpoint runtimeRuntime endpoints
Sáu endpoint POST dùng chung một dạng body; chỉ khác route, thông tin xác thực và vài trường tuỳ chọn.Six POST endpoints share one body shape; they differ in route, credential and a few optional fields.
Body yêu cầu
Request body
| Trường | Field | Bắt buộc | Required | Mô tả | Description |
|---|---|---|---|---|---|
license_key | activate, transfer | activate, transfer | License key (chỉ hai route này) | License key (those two routes only) | |
lease_token | các route khác | other routes | Lease token nhận từ activate | Lease token from activate | |
product | ✔ | ✔ | Slug sản phẩm | Product slug | |
site_url | ✔ | ✔ | URL site/ứng dụng | Site / app URL | |
instance_id | ✔ | ✔ | ID ngẫu nhiên, ổn định của bản cài đặt | Stable random ID of the installation | |
app_version | ✔ | ✔ | Phiên bản sản phẩm đang chạy | Running product version | |
environment | – | – | production · staging · development · local | production · staging · development · local | |
channel | – | – | stable · beta · alpha | stable · beta · alpha | |
transfer_token | transfer | transfer | Mã chuyển thiết bị một lần | One-time device transfer code | |
request_ts, request_nonce | ✔ | ✔ | Xem Chữ ký v2 | See Signature v2 | |
signature_version | ✔ | ✔ | Luôn là 2 | Always 2 | |
request_signature | ✔ | ✔ | HMAC-SHA256 hex | HMAC-SHA256 hex |
Danh sách endpoint
Endpoint list
activate
/api/v2/activateKích hoạt license cho một site/bản cài đặt và cấp lease. Gọi lại với cùng instance_id là idempotent về mặt kích hoạt. Vượt max_activations sẽ bị từ chối.
Activates a license for a site/installation and issues a lease. Repeating the call with the same instance_id does not consume another activation. Exceeding max_activations is rejected.
check
/api/v2/checkKiểm tra lease còn hợp lệ và trả trạng thái license, entitlement, policy hiện tại. Dùng khi khởi động và trước khi mở tính năng trả phí.
Validates the lease and returns the current license status, entitlements and policy. Use it at start-up and before unlocking paid features.
heartbeat
/api/v2/heartbeatBáo cáo bản cài đặt còn hoạt động và tự gia hạn lease khi cần. Gọi định kỳ (ví dụ mỗi 12 giờ) — dưới LEASE_TTL.
Reports that the installation is alive and renews the lease when needed. Call it periodically (e.g. every 12 h) — shorter than LEASE_TTL.
deactivate
/api/v2/deactivateGiải phóng lượt kích hoạt và thu hồi lease của bản cài đặt hiện tại. Sau khi thành công hãy xoá lease token đã lưu.
Frees the activation and revokes the current installation's lease. After success, delete the stored lease token.
update
/api/v2/updateTrả manifest cập nhật có chữ ký; xem trang Cập nhật & tải gói.
Returns a signed update manifest; see Updates & downloads.
transfer/claim
/api/v2/transfer/claimKích hoạt trên thiết bị đích bằng mã chuyển một lần do quản trị viên/khách tạo. Route dùng khi ký là transfer_claim, khoá là license key, và transfer_token nằm trong chuỗi chuẩn hoá.
Activates the destination device using a one-time transfer code created by an admin or the customer. The route used when signing is transfer_claim, the key is the license key, and transfer_token is part of the canonical string.
Ví dụ: check bằng lease
Example: check with a lease
async function call(route, lease, extra = {}) {
const f = { product: 'content-studio-pro', site_url: 'https://example.com',
instance_id: '0fd4c1a2e7b94d6f', app_version: '4.2.0',
environment: 'production', channel: 'stable', ...extra };
const ts = Math.floor(Date.now() / 1000);
const nonce = crypto.randomBytes(16).toString('hex');
const canonical = [route, f.product, f.site_url, f.instance_id, f.app_version,
String(ts), nonce, f.environment, f.channel, f.transfer_token || ''].join('\n');
const sig = crypto.createHmac('sha256', lease).update(canonical).digest('hex');
const r = await fetch(`${BASE}/api/v2/${route}`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...f, lease_token: lease, request_ts: ts,
request_nonce: nonce, signature_version: 2, request_signature: sig }),
});
return r.json();
}
const state = await call('check', leaseToken);
if (!state.valid) lockPaidFeatures();
TS=$(date +%s); NONCE=$(openssl rand -hex 16); LEASE='mbl_…'
CANON=$(printf 'check\ncontent-studio-pro\nhttps://example.com\n0fd4c1a2e7b94d6f\n4.2.0\n%s\n%s\nproduction\nstable\n' "$TS" "$NONCE")
SIG=$(printf '%s' "$CANON" | openssl dgst -sha256 -hmac "$LEASE" -hex | sed 's/^.* //')
curl -sS https://license.example.com/api/v2/check -H 'Content-Type: application/json' -d "{
\"lease_token\":\"$LEASE\",\"product\":\"content-studio-pro\",
\"site_url\":\"https://example.com\",\"instance_id\":\"0fd4c1a2e7b94d6f\",
\"app_version\":\"4.2.0\",\"environment\":\"production\",\"channel\":\"stable\",
\"request_ts\":$TS,\"request_nonce\":\"$NONCE\",
\"signature_version\":2,\"request_signature\":\"$SIG\"}"Nếu valid là false hãy đọc status và message. Đừng suy luận từ mã HTTP: nhiều trạng thái license (hết hạn, bị thu hồi) vẫn trả JSON có cấu trúc.
When valid is false, read status and message. Do not infer from the HTTP code alone: many license states (expired, revoked) still return structured JSON.
Cập nhật & tải góiUpdates & downloads
Phân phối bản phát hành riêng tư với SHA-256, chữ ký Ed25519 và URL tải dùng một lần.Distribute private releases with SHA-256, Ed25519 signatures and single-use download URLs.
Luồng cập nhật
Update flow
- Gọi
POST /api/v2/update(ký bằng lease, có thể gửichannel). - Nếu
update_availablelà true, phản hồi có manifest ký vàdownload_urlmới. - Xác minh chữ ký manifest bằng khoá công khai Ed25519.
- Tải
download_url(GET /api/download/{token}) — chỉ dùng được một lần, hết hạn nhanh. - Tính SHA-256 của file và so với
package_sha256trước khi cài.
- Call
POST /api/v2/update(signed with the lease; optionally sendchannel). - If
update_availableis true, the response carries a signed manifest and a freshdownload_url. - Verify the manifest signature with the Ed25519 public key.
- Fetch
download_url(GET /api/download/{token}) — single use, short-lived. - Compute the file's SHA-256 and compare with
package_sha256before installing.
Phản hồi
Response
| Trường | Field | Ý nghĩa | Meaning |
|---|---|---|---|
update_available | Có bản mới hơn app_version trên kênh yêu cầu | A newer version than app_version exists on the requested channel | |
latest_version, channel | Phiên bản và kênh phát hành | Release version and channel | |
package_sha256, package_size | Băm và kích thước gói ZIP | Hash and size of the ZIP package | |
min_wp_version, min_php_version | Yêu cầu tối thiểu (nếu có) | Minimum requirements (if any) | |
signed_manifest | Manifest JSON mã hoá base64 | Base64-encoded JSON manifest | |
manifest_signature | Chữ ký Ed25519 (base64) của manifest | Ed25519 signature (base64) over the manifest | |
signing_key_id, signing_algorithm | ID khoá ký và thuật toán (Ed25519) | Signing key ID and algorithm (Ed25519) | |
download_url | URL tải dùng một lần; chỉ có khi có bản cập nhật | Single-use URL; present only when an update exists |
Xác minh manifest
Verifying the manifest
Khoá công khai (RELEASE_SIGNING_PUBLIC_KEY_B64) được nhúng sẵn vào sản phẩm. Manifest có các trường product, version, channel, sha256, size, min_wp, min_php, published_at, key_id; sau khi kiểm chữ ký, hãy đối chiếu product, version, channel, sha256 và size với phần còn lại của phản hồi.
The public key (RELEASE_SIGNING_PUBLIC_KEY_B64) is embedded in your product. The manifest holds product, version, channel, sha256, size, min_wp, min_php, published_at, key_id; after checking the signature, cross-check product, version, channel, sha256 and size against the rest of the response.
const crypto = require('crypto');
function verifyManifest(resp, publicKeyB64, expectedProduct) {
const message = Buffer.from(resp.signed_manifest, 'base64');
const signature = Buffer.from(resp.manifest_signature, 'base64');
// Wrap the raw 32-byte Ed25519 key in an SPKI header
const spki = Buffer.concat([
Buffer.from('302a300506032b6570032100', 'hex'),
Buffer.from(publicKeyB64, 'base64'),
]);
const key = crypto.createPublicKey({ key: spki, format: 'der', type: 'spki' });
if (!crypto.verify(null, message, key, signature)) return false;
const m = JSON.parse(message.toString('utf8'));
return m.product === expectedProduct && m.version === resp.latest_version
&& m.sha256.toLowerCase() === resp.package_sha256.toLowerCase()
&& m.size === resp.package_size;
}Chỉ cài gói khi cả chữ ký manifest và SHA-256 của file tải về đều khớp. Làm mới URL ngay trước khi tải vì token hết hạn nhanh.
Install only when both the manifest signature and the downloaded file's SHA-256 match. Refresh the URL right before downloading — tokens expire quickly.
Integration API
Cho backend của bạn (cổng thanh toán, CRM, cửa hàng) đọc danh mục sản phẩm, license và ghi nhận đơn hàng.For your backend (payment gateway, CRM, storefront) to read the catalog and licenses and to record orders.
Xác thực
Authentication
Gửi hai header trên mọi yêu cầu. Thiếu scope hoặc sai IP/CIDR sẽ bị từ chối; đăng nhập sai nhiều lần bị giới hạn tốc độ.
Send two headers on every request. A missing scope or a non-allowlisted IP/CIDR is rejected; repeated bad credentials are rate limited.
X-MB-Client: your-client-id X-MB-Secret: your-client-secret
Liệt kê sản phẩm
List products
/api/integration/v1/productsscope product:readscope product:readcurl -s https://license.example.com/api/integration/v1/products \
-H "X-MB-Client: $CLIENT_ID" -H "X-MB-Secret: $CLIENT_SECRET"
# {"ok":true,"products":[{"id":1,"name":"Content Studio Pro","slug":"content-studio-pro",
# "status":"active","current_version":"4.3.0"}],"server_time":"…"}Liệt kê license
List licenses
/api/integration/v1/licenses?limit=50scope license:readscope license:readlimit mặc định 50, tối đa 100; kết quả mới nhất trước. License key đầy đủ không bao giờ được trả — chỉ có key_hint.
limit defaults to 50, max 100; newest first. The full license key is never returned — only a key_hint.
{
"ok": true,
"licenses": [
{ "id": 12, "key_hint": "…A1B2", "plan": "pro", "status": "active",
"customer_id": 3, "organization_id": 0, "expires_at": "2027-11-12T00:00:00Z",
"max_activations": 1, "product": "content-studio-pro" }
],
"server_time": "2026-11-12T08:00:00Z"
}Tạo / cập nhật đơn hàng
Create / update an order
/api/integration/v1/ordersscope order:writescope order:write| Trường | Field | Kiểu | Type | Ghi chú | Notes |
|---|---|---|---|---|---|
provider | string | string | Mặc định manual; ≤ 40 ký tự | Default manual; ≤ 40 chars | |
external_order_id | string | string | ≤ 120 ký tự. Cùng provider + external_order_id sẽ upsert (idempotent) | ≤ 120 chars. The same provider + external_order_id is an upsert (idempotent) | |
customer_id, organization_id, license_id, product_id | int | int | ID nội bộ liên kết | Internal IDs to link | |
status | string | string | Mặc định active | Default active | |
amount_minor | int | int | Đơn vị nhỏ nhất, ≥ 0 | Minor units, ≥ 0 | |
currency | string | string | 3 ký tự; mặc định VND | 3 letters; default VND | |
renewal_at | RFC 3339 | RFC 3339 | Ngày gia hạn dự kiến | Expected renewal date | |
metadata | object | object | Giá trị nhạy cảm được tự động che | Sensitive values are redacted automatically |
curl -sS -X POST https://license.example.com/api/integration/v1/orders \
-H "X-MB-Client: $CLIENT_ID" -H "X-MB-Secret: $CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{"provider":"stripe","external_order_id":"ord_1042","license_id":12,
"product_id":1,"status":"active","amount_minor":199000,"currency":"vnd",
"renewal_at":"2027-11-12T00:00:00Z"}'
# 201 {"ok":true,"order":{"id":57}}Body tối đa 64 KiB. Lỗi trả về dạng ngắn gọn, ví dụ invalid_json, invalid_order.
Body is capped at 64 KiB. Errors are short codes such as invalid_json and invalid_order.
Reseller API
Đại lý cấp license trực tiếp từ hệ thống bán hàng của mình, trừ vào quota đã được cấp.Resellers issue licenses straight from their own storefront, drawing down their assigned quota.
Xác thực & giới hạn
Authentication & limits
Authorization: Bearer mbr_YOUR_KEY
Giới hạn 120 yêu cầu/phút cho mỗi key; vượt quá nhận 429 rate_limited. Key sai hoặc đại lý bị khoá nhận 401 api_key_invalid.
The limit is 120 requests per minute per key; beyond it you get 429 rate_limited. A wrong key or a suspended reseller gets 401 api_key_invalid.
Xem quota còn lại
Remaining quota
/api/v2/reseller/quotacurl -s https://license.example.com/api/v2/reseller/quota \
-H "Authorization: Bearer mbr_YOUR_KEY"
# {"ok":true,"quotas":[{"product":"content-studio-pro","name":"Content Studio Pro",
# "total":100,"used":37,"remaining":63,"expires_at":"2027-06-30T00:00:00Z"}]}Cấp license
Issue a license
/api/v2/reseller/licenses| Trường | Field | Bắt buộc | Required | Mô tả | Description |
|---|---|---|---|---|---|
product | ✔ | ✔ | Slug sản phẩm có trong quota của bạn | Product slug present in your quota | |
customer_name | ✔ | ✔ | ≤ 200 ký tự | ≤ 200 chars | |
customer_email | ✔ | ✔ | Email hợp lệ, ≤ 254 ký tự | Valid email, ≤ 254 chars | |
duration | – | – | 1m · 3m · 6m · 12m · lifetime (mặc địnhdefault 12m) | 1m · 3m · 6m · 12m · lifetime | |
max_activations | – | – | Số site được kích hoạt | Number of allowed sites |
curl -sS -X POST https://license.example.com/api/v2/reseller/licenses \
-H "Authorization: Bearer mbr_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"product":"content-studio-pro","customer_name":"Nguyen Van A",
"customer_email":"a@example.com","duration":"12m","max_activations":1}'
const r = await fetch('https://license.example.com/api/v2/reseller/licenses', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.MBR_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ product: 'content-studio-pro', customer_name: 'Nguyen Van A',
customer_email: 'a@example.com', duration: '12m', max_activations: 1 }),
});
const out = await r.json();
if (r.status === 201) console.log(out.license_key, out.quota_remaining);
<?php
$ch = curl_init('https://license.example.com/api/v2/reseller/licenses');
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('MBR_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'product' => 'content-studio-pro', 'customer_name' => 'Nguyen Van A',
'customer_email' => 'a@example.com', 'duration' => '12m', 'max_activations' => 1,
]),
]);
$out = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);{
"ok": true,
"license_id": 88,
"license_key": "MB-XXXX-XXXX-XXXX-XXXX",
"product": "content-studio-pro",
"expires_at": "2027-11-12T08:00:00Z",
"quota_remaining": 62
}license_key không thể truy xuất lại. Hãy lưu hoặc chuyển cho khách ngay. Mỗi lần gọi thành công trừ một đơn vị quota, vì vậy đừng tự động thử lại khi chưa chắc yêu cầu trước đã thất bại.
license_key cannot be retrieved again. Store it or hand it to the customer immediately. Every successful call consumes one quota unit, so do not blindly retry unless you are sure the previous request failed.
Lỗi & giới hạn tốc độErrors & rate limits
Cách đọc lỗi và cách xử lý từng loại.How to read errors and how to react to each kind.
Dạng lỗi
Error shape
{ "ok": false, "error": "signature_invalid", "message": "Request signature is invalid." }Hãy dựa vào error (ổn định, dành cho máy) thay vì message (dành cho người đọc, có thể đổi).
Branch on error (stable, machine-readable), not on message (human text that may change).
Mã lỗi thường gặp
Common error codes
| HTTP | error | Nguyên nhân & cách xử lý | Cause & remedy |
|---|---|---|---|
| 400 | invalid_payload / invalid_json / invalid_request | Body sai định dạng hoặc thiếu trường. Sửa yêu cầu, đừng thử lại nguyên văn. | Malformed body or missing fields. Fix the request; do not retry verbatim. |
| 400 | invalid_duration | duration không thuộc 1m/3m/6m/12m/lifetime. | duration is not one of 1m/3m/6m/12m/lifetime. |
| 401 | signature_invalid | Chữ ký sai hoặc lệch giờ quá REQUEST_SKEW. Kiểm tra chuỗi chuẩn hoá, khoá, site_url đã chuẩn hoá và đồng hồ. | Wrong signature or clock skew beyond REQUEST_SKEW. Check the canonical string, key, normalized site_url and your clock. |
| 401 | api_key_invalid | Reseller key sai hoặc bị khoá. | Wrong or suspended reseller key. |
| 404 | product_not_found | Sản phẩm không nằm trong quota của đại lý. | Product is not in the reseller's quota. |
| 409 | signature_replay | Nonce đã dùng. Tạo nonce mới cho mỗi lần gọi. | Nonce already used. Generate a new nonce per call. |
| 409 | quota_exhausted | Hết hoặc quá hạn quota. Xin thêm quota từ portal. | Quota exhausted or expired. Request a top-up in the portal. |
| 413 | body_too_large | Body runtime vượt 32 KiB. | Runtime body exceeds 32 KiB. |
| 426 | signature_version | Thiếu signature_version: 2. | Missing signature_version: 2. |
| 429 | rate_limited | Quá giới hạn tốc độ; chờ rồi thử lại với backoff. | Rate limit hit; wait and retry with backoff. |
| 5xx | internal_error … | Lỗi máy chủ. Thử lại có backoff; với Reseller API cần kiểm tra quota trước khi thử lại. | Server error. Retry with backoff; for the Reseller API check quota before retrying. |
Danh sách trên là các mã đã xác nhận trong mã nguồn; trạng thái license (hết hạn, bị thu hồi, vượt số lần kích hoạt…) được báo qua valid, status và message trong phản hồi.
These are the codes confirmed in the source; license states (expired, revoked, activation limit reached…) are reported through valid, status and message in the response.
Giới hạn tốc độ
Rate limits
| API | Giới hạn | Limit |
|---|---|---|
| Runtime | 120 yêu cầu/phút cho mỗi (route, IP). Header X-RateLimit-Remaining và X-RateLimit-Reset có trong phản hồi. | 120 requests/min per (route, IP). X-RateLimit-Remaining and X-RateLimit-Reset headers are returned. |
| Reseller | 120 yêu cầu/phút cho mỗi key. | 120 requests/min per key. |
| Integration | Có giới hạn theo client; header giới hạn được trả kèm phản hồi. | Limited per client; limit headers accompany responses. |
Giới hạn theo IP dùng IP thật của client chỉ khi proxy của bạn nằm trong TRUSTED_PROXIES. Cấu hình sai sẽ khiến mọi yêu cầu chung một bucket.
Per-IP limits use the real client IP only when your proxy is listed in TRUSTED_PROXIES. A misconfiguration puts every request in one bucket.
SDK WordPress (PHP)WordPress SDK (PHP)
Nếu sản phẩm là plugin WordPress, dùng SDK có sẵn thay vì tự ký yêu cầu.If your product is a WordPress plugin, use the bundled SDK instead of signing requests yourself.
File: sdk/php/class-mblc-wordpress-license-client.php. SDK xử lý lưu lease, Signature v2, chế độ offline grace, circuit breaker, heartbeat nền, manifest cập nhật có chữ ký và kiểm SHA-256 gói.
File: sdk/php/class-mblc-wordpress-license-client.php. The SDK handles lease persistence, Signature v2, offline grace, a circuit breaker, background heartbeat, signed update manifests and package SHA-256 verification.
Cài đặt
Setup
<?php
require_once __DIR__ . '/class-mblc-wordpress-license-client.php';
$client = new MBLC_WordPress_License_Client([
'server_url' => 'https://license.example.com',
'api_base' => '/api/v2',
'product' => 'my-plugin-pro',
'plugin_file' => plugin_basename(__FILE__),
'version' => MY_PLUGIN_VERSION,
'license_key' => get_option('my_plugin_license_key', ''),
'channel' => 'stable',
'release_public_key' => 'BASE64_ED25519_PUBLIC_KEY',
'strict_signing' => true,
]);
$client->register_updater();Sử dụng
Usage
$result = $client->activate(); // after the user submits a license key
if (is_wp_error($result) || empty($result['valid'])) { /* show message */ }
$state = $client->check(); // uses the stored lease automatically
$client->heartbeat();
$client->deactivate(); // also clears the stored leasestrict_signingyêu cầu xác minh được chữ ký manifest; đểtruetrên môi trường thật.- SDK mặc định đòi máy chủ dùng HTTPS (trừ
localhost,*.local,*.test). - Khi lease thiếu hoặc lỗi mạng, SDK có thể dùng trạng thái đã lưu cho tới hết thời gian ân hạn.
strict_signingrequires the manifest signature to verify; keep ittruein production.- By default the SDK insists on an HTTPS server (except
localhost,*.local,*.test). - On a missing lease or network failure the SDK may serve the stored state until the grace window ends.
Ngôn ngữ khác
Other languages
Chưa có SDK chính thức cho ngôn ngữ khác. Hãy làm theo Chữ ký yêu cầu v2: chỉ cần HMAC-SHA256, bộ sinh số ngẫu nhiên an toàn và (cho cập nhật) xác minh Ed25519 cùng SHA-256.
There is no official SDK for other languages yet. Follow Request Signature v2: you only need HMAC-SHA256, a secure random generator and (for updates) Ed25519 verification plus SHA-256.
Danh sách trước khi chạy thậtGo-live checklist
Rà soát nhanh trước khi phát hành tích hợp cho khách hàng.A quick review before you ship your integration to customers.
Phía sản phẩm (client)
Product side (client)
- Ký mọi yêu cầu bằng Signature v2; nonce mới cho mỗi lần gọi, kể cả khi thử lại.
- Ký
site_urlở dạng đã chuẩn hoá. - Đồng bộ giờ hệ thống (NTP); lệch quá 5 phút sẽ bị từ chối.
- Chỉ gửi license key lúc activate/transfer, sau đó chỉ dùng lease token.
- Lưu lease token ở nơi an toàn (không log, không gửi telemetry).
heartbeatđịnh kỳ ngắn hơnLEASE_TTL; hỗ trợ ân hạn offline và khoá tính năng trả phí khi hết ân hạn.- Xác minh chữ ký manifest Ed25519 và SHA-256 trước khi cài bản cập nhật.
- Dùng HTTPS, kiểm chứng chứng chỉ; backoff luỹ thừa khi nhận 429/5xx.
- Sign every request with Signature v2; use a fresh nonce per call, retries included.
- Sign the normalized
site_url. - Keep the system clock on NTP; more than 5 minutes of skew is rejected.
- Send the license key only for activate/transfer; use the lease token afterwards.
- Store the lease token securely (no logs, no telemetry).
- Run
heartbeatmore often thanLEASE_TTL; support offline grace and lock paid features when it ends. - Verify the Ed25519 manifest signature and SHA-256 before installing an update.
- Use HTTPS with certificate validation; back off exponentially on 429/5xx.
Phía máy chủ (backend)
Server side (backend)
- Giữ API client secret và
mbr_key trong secret manager; cấp scope tối thiểu và giới hạn IP/CIDR. - Xoay secret khi nhân sự thay đổi hoặc nghi ngờ lộ.
- Dùng
external_order_idđể thao tác đơn hàng idempotent. - Với Reseller API: kiểm tra
quota_remainingvà đừng thử lại mù quáng yêu cầu cấp license. - Cấu hình
TRUSTED_PROXIESđúng khi chạy sau reverse proxy.
- Keep API client secrets and
mbr_keys in a secret manager; grant minimal scopes and restrict by IP/CIDR. - Rotate secrets on staff changes or suspected leaks.
- Use
external_order_idto make order writes idempotent. - For the Reseller API: watch
quota_remainingand never blindly retry an issue request. - Set
TRUSTED_PROXIEScorrectly when running behind a reverse proxy.
Vận hành
Operations
Xem thêm docs/PRODUCTION-CHECKLIST.md, docs/SECURITY.md và docs/DEPLOY-DOCKER-VI.md trong repository.
See also docs/PRODUCTION-CHECKLIST.md, docs/SECURITY.md and docs/DEPLOY-DOCKER-VI.md in the repository.