Ký yêu cầu POST nền tảng VS Open
Phiên bản: V1.0
Sửa đổi: 2026-03-16
Phạm vi: Mọi API POST của VS Open Platform
1. Mục đích
Mô tả cách ký và xác minh yêu cầu POST. Thiếu chữ ký hoặc sai quy tắc sẽ bị từ chối. Tài liệu gồm luật, các bước ký, mẫu SDK và xử lý sự cố.
2. Thuật ngữ
| Thuật ngữ | Định nghĩa |
|---|---|
| API Key | Định danh caller từ portal; biến môi trường VS_OPEN_API_KEY; gửi qua X-API-KEY |
| Secret Key | Khóa ký; biến VS_OPEN_SECRET_KEY; không gửi lên mạng |
| Base URL | Cổng API gốc; VS_OPEN_API_BASE_URL, ví dụ https://api.valuescan.io/api/open/v1 |
| X-TIMESTAMP | Timestamp milli 13 chữ số; chống replay |
| X-SIGN | HMAC-SHA256, hex chữ thường, đảm bảo toàn vẹn body |
| Raw Body | Chuỗi body POST giống hệt — không được format lại |
| Path | Đường dẫn tương đối, ví dụ /api/v1/order/create, nối sau Base URL |
3. Vì sao cần ký
- Nhận diện: API Key xác nhận caller được cấp quyền
- Toàn vẹn: Chữ ký phát hiện body bị sửa
- Replay: Timestamp giới hạn cửa sổ (mặc định ±5 phút phía server)
- An toàn: Secret chỉ tồn tại phía bạn
4. Quy tắc
- Chỉ POST; body dùng để ký phải trùng byte với body gửi đi (JSON, form, v.v.)
- Không được trim, re-indent, đổi thứ tự key
- Timestamp milli 13 chữ số; lệch giờ server ≤ 5 phút
- Mọi thứ là UTF-8
- X-SIGN = HMAC-SHA256 hex thường (64 ký tự hex); không Base64 / HOA
5. Luồng xử lý
5.1 Tổng quan
flowchart TD
A[Lấy API Key / Secret / Base URL] --> B[Sinh timestamp milli 13 chữ số]
B --> C[Đọc raw body POST]
C --> D[signContent = timestamp + raw body]
D --> E[HMAC-SHA256 với Secret Key]
E --> F[Hex digest chữ thường]
F --> G[Đặt header X-API-KEY / X-TIMESTAMP / X-SIGN]
G --> H[POST tới Base URL + Path]5.2 Các bước
Bước 1 — Cấu hình
Từ console VS Open Platform, lấy khóa và đặt biến môi trường:
Linux / macOS:
bashexport VS_OPEN_API_KEY="YOUR_API_KEY" export VS_OPEN_SECRET_KEY="YOUR_SECRET_KEY" export VS_OPEN_API_BASE_URL="YOUR_BASE_URL (e.g. https://api.valuescan.io/api/open/v1)"Windows CMD:
cmdset VS_OPEN_API_KEY=YOUR_API_KEY set VS_OPEN_SECRET_KEY=YOUR_SECRET_KEY set VS_OPEN_API_BASE_URL=YOUR_BASE_URL (e.g. https://api.valuescan.io/api/open/v1)
Bước 2 — Timestamp
Milli 13 chữ số, ví dụ:
- Python:
str(int(time.time() * 1000)) - Java:
String.valueOf(System.currentTimeMillis()) - JavaScript:
Date.now().toString()
Bước 3 — Raw body
Dùng đúng chuỗi gửi trong POST; nếu rỗng, dùng "".
Bước 4 — Chuỗi cần ký
Không ký tự phân tách:
signContent = X-TIMESTAMP + RawBodyBước 5 — HMAC-SHA256
Key = Secret Key; message = signContent; đầu ra = hex chữ thường.
Tham chiếu phía server (Java):
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import java.nio.charset.StandardCharsets;
public static String hmacSign(String content, String hmacKey) {
if (content == null || content.isEmpty() || hmacKey == null || hmacKey.isEmpty()) {
throw new IllegalArgumentException("Content and key required");
}
HMac hMac = new HMac(HmacAlgorithm.HmacSHA256, hmacKey.getBytes(StandardCharsets.UTF_8));
return hMac.digestHex(content);
}Bước 6 — Header
| Header | Giá trị |
|---|---|
| X-API-KEY | VS_OPEN_API_KEY |
| X-TIMESTAMP | Bước 2 |
| X-SIGN | Bước 5 |
Bước 7 — Gửi
POST tới Base URL + Path với cùng raw body.
6. Ví dụ SDK
6.1 Python
6.1.1 Ví dụ đầy đủ (Base URL + path)
import os
import hmac
import hashlib
import time
import requests
from urllib.parse import urljoin
BASE_URL = os.getenv("VS_OPEN_API_BASE_URL")
if not BASE_URL:
raise EnvironmentError("Set VS_OPEN_API_BASE_URL")
class VSAPISign:
"""Sign and send POST for VS Open Platform."""
@staticmethod
def get_sign_headers(raw_body: str) -> dict:
api_key = os.getenv("VS_OPEN_API_KEY")
secret_key = os.getenv("VS_OPEN_SECRET_KEY")
if not api_key or not secret_key:
raise ValueError(
"Missing VS_OPEN_API_KEY or VS_OPEN_SECRET_KEY\n"
"Linux/Mac: export VS_OPEN_API_KEY='...' && export VS_OPEN_SECRET_KEY='...'\n"
"Windows: set VS_OPEN_API_KEY=... && set VS_OPEN_SECRET_KEY=..."
)
timestamp = str(int(time.time() * 1000))
sign_content = timestamp + raw_body
hmac_obj = hmac.new(
secret_key.encode("utf-8"),
sign_content.encode("utf-8"),
digestmod=hashlib.sha256
)
sign = hmac_obj.hexdigest()
return {
"X-API-KEY": api_key,
"X-TIMESTAMP": timestamp,
"X-SIGN": sign,
"Content-Type": "application/json; charset=utf-8"
}
@staticmethod
def send_post_request(path: str, raw_body: str, timeout: int = 10) -> requests.Response:
full_url = urljoin(BASE_URL, path)
headers = VSAPISign.get_sign_headers(raw_body)
return requests.post(
url=full_url,
headers=headers,
data=raw_body.encode("utf-8"),
timeout=timeout
)
if __name__ == "__main__":
api_path = "/api/v1/order/create"
raw_body = """{
"order_no": "ORD20260316001",
"amount": 100.00,
"product_id": "P10001"
}"""
try:
response = VSAPISign.send_post_request(api_path, raw_body)
print(f"Status: {response.status_code}")
print(f"Body: {response.text}")
except Exception as e:
print(f"Error: {str(e)}")6.1.2 Ghi chú
- Python 3.6+,
pip install requests - Đặt ba biến môi trường như §5.2.1
- Gọi
send_post_request(path, raw_body) - Điều chỉnh
Content-Typenếu body không phải JSON
6.2 Node.js
6.2.1 Ví dụ đầy đủ
/**
* VS Open Platform POST signing (Node.js)
* Node 16+: built-in crypto, fetch, url
*/
const crypto = require('crypto');
const process = require('process');
const { URL } = require('url');
const BASE_URL = process.env.VS_OPEN_API_BASE_URL;
if (!BASE_URL) {
throw new Error('Set VS_OPEN_API_BASE_URL');
}
function buildSignHeader(rawBody) {
const apiKey = process.env.VS_OPEN_API_KEY;
const secretKey = process.env.VS_OPEN_SECRET_KEY;
if (!apiKey) throw new Error('VS_OPEN_API_KEY not set');
if (!secretKey) throw new Error('VS_OPEN_SECRET_KEY not set');
const timestamp = Date.now().toString();
const signContent = timestamp + rawBody;
const hmac = crypto.createHmac('sha256', secretKey);
hmac.update(signContent, 'utf8');
const sign = hmac.digest('hex');
return {
'X-API-KEY': apiKey,
'X-TIMESTAMP': timestamp,
'X-SIGN': sign,
'Content-Type': 'application/json; charset=utf-8',
'Accept': '*/*'
};
}
async function vsPost(path, data, timeout = 10000) {
const rawBody = typeof data === 'object' ? JSON.stringify(data) : data;
const fullUrl = new URL(path, BASE_URL).href;
const headers = buildSignHeader(rawBody);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(fullUrl, {
method: 'POST',
headers,
body: rawBody,
signal: controller.signal
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(`Timeout (${timeout}ms): ${fullUrl}`);
}
throw new Error(`Request failed: ${error.message}`);
}
}
module.exports = { buildSignHeader, vsPost };
if (require.main === module) {
const apiPath = '/api/v1/order/create';
const requestData = {
order_no: "ORD20260316001",
amount: 100.00,
product_id: "P10001"
};
vsPost(apiPath, requestData)
.then(result => {
console.log('OK:', JSON.stringify(result, null, 2));
})
.catch(error => {
console.error('Error:', error.message);
});
}6.2.2 Ghi chú
- Node 16+
- Biến môi trường (bash/cmd như mục Python)
- Import
vsPost(path, data) - Điều chỉnh
Content-Typecho form nếu cần
7. Mẫu end-to-end
7.1 Đầu vào
Base URL:
https://api.valuescan.io/api/open/v1API Key:
VS_API_20260316001Secret:
VS_SECRET_8e9f7d6c5b4a3210Path:
/api/v1/order/createBody:
json{ "user_id": "U10001", "action": "create_order", "params": {"goods_id": "G001", "num": 2} }Timestamp:
1710585600000
7.2 Chuỗi ký
Nối timestamp + raw JSON (giữ nguyên khoảng trắng):
1710585600000{
"user_id": "U10001",
"action": "create_order",
"params": {"goods_id": "G001", "num": 2}
}Ví dụ digest: 5f8d7e6c5b4a32109876543210abcdef5f8d7e6c5b4a32109876543210abcdef
7.3 Yêu cầu
URL:
https://api.valuescan.io/api/v1/order/createHeaders:
httpX-API-KEY: VS_API_20260316001 X-TIMESTAMP: 1710585600000 X-SIGN: 5f8d7e6c5b4a32109876543210abcdef5f8d7e6c5b4a32109876543210abcdef Content-Type: application/json; charset=utf-8Body: identical raw string used for signing