Skip to content

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 KeyKhóa ký; biến VS_OPEN_SECRET_KEY; không gửi lên mạng
Base URLCổng API gốc; VS_OPEN_API_BASE_URL, ví dụ https://api.valuescan.io/api/open/v1
X-TIMESTAMPTimestamp milli 13 chữ số; chống replay
X-SIGNHMAC-SHA256, hex chữ thường, đảm bảo toàn vẹn body
Raw BodyChuỗ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

  1. Chỉ POST; body dùng để ký phải trùng byte với body gửi đi (JSON, form, v.v.)
  2. Không được trim, re-indent, đổi thứ tự key
  3. Timestamp milli 13 chữ số; lệch giờ server ≤ 5 phút
  4. Mọi thứ là UTF-8
  5. 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

mermaid
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:

    bash
    export 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:

    cmd
    set 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 + RawBody

Bước 5 — HMAC-SHA256

Key = Secret Key; message = signContent; đầu ra = hex chữ thường.

Tham chiếu phía server (Java):

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

HeaderGiá trị
X-API-KEYVS_OPEN_API_KEY
X-TIMESTAMPBước 2
X-SIGNBướ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)

python
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ú

  1. Python 3.6+, pip install requests
  2. Đặt ba biến môi trường như §5.2.1
  3. Gọi send_post_request(path, raw_body)
  4. Điều chỉnh Content-Type nếu body không phải JSON

6.2 Node.js

6.2.1 Ví dụ đầy đủ

javascript
/**
 * 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ú

  1. Node 16+
  2. Biến môi trường (bash/cmd như mục Python)
  3. Import vsPost(path, data)
  4. Điều chỉnh Content-Type cho form nếu cần

7. Mẫu end-to-end

7.1 Đầu vào

  • Base URL: https://api.valuescan.io/api/open/v1

  • API Key: VS_API_20260316001

  • Secret: VS_SECRET_8e9f7d6c5b4a3210

  • Path: /api/v1/order/create

  • Body:

    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/create

  • Headers:

    http
    X-API-KEY: VS_API_20260316001
    X-TIMESTAMP: 1710585600000
    X-SIGN: 5f8d7e6c5b4a32109876543210abcdef5f8d7e6c5b4a32109876543210abcdef
    Content-Type: application/json; charset=utf-8
  • Body: identical raw string used for signing