Skip to content

VS Open Platform POST request signing

Version: V1.0 Revised: 2026-03-16 Scope: All VS Open Platform POST APIs

1. Purpose

Defines how POST requests are signed and verified. Unsigned or invalid signatures are rejected. Covers rules, step-by-step signing, SDK samples, and troubleshooting.

2. Terms

TermDefinition
API KeyCaller id from the portal; env VS_OPEN_API_KEY; sent as X-API-KEY
Secret KeySigning secret; env VS_OPEN_SECRET_KEY; never send over the wire
Base URLGateway base; env VS_OPEN_API_BASE_URL, e.g. https://api.valuescan.io/api/open/v1
X-TIMESTAMP13-digit millisecond timestamp; replay protection
X-SIGNHMAC-SHA256 hex lowercase digest of payload integrity
Raw BodyExact POST body string no reformatting
PathRelative API path, e.g. /api/v1/order/create, appended to Base URL

3. Why sign

  • Identity: API Key proves an authorized caller
  • Integrity: Signature detects body tampering
  • Replay: Timestamp bounds validity (default ±5 minutes server-side)
  • Safety: Secret never leaves your environment

4. Rules

  1. POST only; body must be the exact raw string used for signing (JSON, form body, etc.)
  2. Byte-identical body no trimming, re-indenting, key reordering
  3. 13-digit ms timestamp; skew within 5 minutes of server time
  4. UTF-8 everywhere
  5. X-SIGN = HMAC-SHA256 hex lowercase (64 hex chars); not Base64 or uppercase

5. Flow

5.1 Overview

mermaid
flowchart TD
 A[Get API Key / Secret / Base URL] --> B[Generate 13-digit ms timestamp]
 B --> C[Read raw POST body]
 C --> D[signContent = timestamp + raw body]
 D --> E[HMAC-SHA256 with Secret Key]
 E --> F[Hex digest lowercase]
 F --> G[Set X-API-KEY / X-TIMESTAMP / X-SIGN headers]
 G --> H[POST to Base URL + Path]

5.2 Steps

Step 1 — Configure

From VS Open Platform console, obtain keys and set env vars:

  • 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)

Step 2 — Timestamp

13-digit milliseconds, e.g.:

  • Python: str(int(time.time() * 1000))
  • Java: String.valueOf(System.currentTimeMillis())
  • JavaScript: Date.now().toString()

Step 3 — Raw body

Use the exact string sent in the POST body; if empty, use "".

Step 4 — String to sign

No separator:

signContent = X-TIMESTAMP + RawBody

Step 5 — HMAC-SHA256

Key = Secret Key; message = signContent; output = hex lowercase.

Server reference (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);
}

Step 6 — Headers

HeaderValue
X-API-KEYVS_OPEN_API_KEY
X-TIMESTAMPFrom step 2
X-SIGNFrom step 5

Step 7 — Send

POST to Base URL + Path with the same raw body.

6. SDK examples

6.1 Python

6.1.1 Full example (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 Notes

  1. Python 3.6+, pip install requests
  2. Set the three env vars from §5.2.1
  3. Call send_post_request(path, raw_body)
  4. Adjust Content-Type for non-JSON bodies if needed

6.2 Node.js

6.2.1 Full example

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 Notes

  1. Node 16+
  2. Env vars (bash/cmd as in Python section)
  3. Import vsPost(path, data)
  4. Adjust Content-Type for form bodies if needed

7. End-to-end sample

7.1 Inputs

  • 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 Sign payload

Concatenate timestamp + raw JSON (exact spacing):

1710585600000{
 "user_id": "U10001",
 "action": "create_order",
 "params": {"goods_id": "G001", "num": 2}
}

Example digest: 5f8d7e6c5b4a32109876543210abcdef5f8d7e6c5b4a32109876543210abcdef

7.3 Request

  • 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