生产环境 API 文档

HStore API 文档

Base URLhttps://hstore.site/api/v1 版本v1 默认限流60/min
认证
HMAC-SHA256
时间戳窗口
5 分钟
幂等性
24 小时
Webhook 重试
最多 6 次

快速开始

  1. 在 API Keys 页面为每个集成创建单独的 API 密钥。
  2. 只在服务端签名。不要在前端代码中暴露 secret。
  3. 先读取 /catalog,再用 /products/{id} 刷新目标商品,然后再创建订单。
  4. 在 POST /orders 中始终发送稳定的 external_order_id 和 Idempotency-Key。
  5. 在重试或超时后,使用 /orders/{id} 或 /orders/lookup 做对账。

认证与请求模型

请求头 必填 说明
X-API-Key 公开的密钥标识。
X-Timestamp 秒级 Unix 时间戳。必须在 5 分钟窗口内。
X-Nonce 每次请求都应唯一的随机字符串,用于阻止重放。
X-Signature 对规范请求计算出的 HMAC-SHA256 小写十六进制签名。
Idempotency-Key POST /orders 创建订单时必填。重试同一笔购买时复用它。
Origin / Referer 仅当配置了允许域名时 启用域名限制时,必须与某个已配置域名完全匹配。
签名规范字符串
METHOD
PATH
QUERY
TIMESTAMP
NONCE
BODY_HASH
规则
  • PATH 必须与实际请求路径完全一致,包括 /api/v1。
  • QUERY 是原始查询字符串,不包含 ?,顺序必须与发送时一致。
  • BODY_HASH 在 GET 时为空,在 POST 时为原始 JSON body 的 SHA256。
  • X-Signature 必须是小写十六进制 HMAC-SHA256。
  • 如果配置了允许域名,Origin 或 Referer 必须完全匹配。

接口

方法 路径 说明
GET /api/v1/ API 元数据以及支持的 webhook 事件。
GET /api/v1/catalog 按分页和筛选条件列出活动商品。
GET /api/v1/products/{id} 在结账前获取完整商品详情。
GET /api/v1/balance 读取当前钱包余额和待结算余额。
POST /api/v1/orders 使用 Main Wallet 创建并扣款订单。
GET /api/v1/orders/{id} 按 HStore 内部订单 ID 读取订单。
GET /api/v1/orders/lookup?external_order_id=... 通过你的外部订单号找回订单。

示例

API 元数据
curl -X GET 'https://hstore.site/api/v1/'
$response = file_get_contents('https://hstore.site/api/v1/');
echo $response;
const response = await fetch("https://hstore.site/api/v1/");
console.log(await response.json());
import requests

print(requests.get("https://hstore.site/api/v1/").json())
Invoke-RestMethod -Uri "https://hstore.site/api/v1/" -Method Get | ConvertTo-Json -Depth 20
响应示例
{
    "success": true,
    "data": {
        "name": "HStore Partner API",
        "version": "v1",
        "docs_url": "https://hstore.site/api-doc",
        "webhooks": {
            "supported_events": [
                "product.stock_changed",
                "product.price_changed",
                "order.created",
                "order.delivered"
            ]
        }
    }
}
获取目录列表
API_KEY="hs_pk_xxxxxxxxxxxxxxxx"
API_SECRET="hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
METHOD="GET"
PATH="/api/v1/catalog"
QUERY="page=1&limit=20"
TIMESTAMP=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=""
CANONICAL="$METHOD
$PATH
$QUERY
$TIMESTAMP
$NONCE
$BODY_HASH"
SIGNATURE=$(printf "%s" "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed "s/^.* //")

curl -X GET 'https://hstore.site/api/v1/catalog?page=1&limit=20' \
  -H "X-API-Key: $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"
$apiKey = 'hs_pk_xxxxxxxxxxxxxxxx';
$apiSecret = 'hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$method = 'GET';
$path = '/api/v1/catalog';
$query = 'page=1&limit=20';
$body = '';
$timestamp = (string) time();
$nonce = bin2hex(random_bytes(16));
$bodyHash = $body === '' ? '' : hash('sha256', $body);
$canonical = implode("\n", [$method, $path, $query, $timestamp, $nonce, $bodyHash]);
$signature = hash_hmac('sha256', $canonical, $apiSecret);

$headers = [
    'X-API-Key: ' . $apiKey,
    'X-Timestamp: ' . $timestamp,
    'X-Nonce: ' . $nonce,
    'X-Signature: ' . $signature,
];

$ch = curl_init('https://hstore.site/api/v1/catalog?page=1&limit=20');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => $method,
    CURLOPT_HTTPHEADER => $headers,
]);

$response = curl_exec($ch);
curl_close($ch);
echo $response;
import crypto from "node:crypto";

const apiKey = "hs_pk_xxxxxxxxxxxxxxxx";
const apiSecret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const method = "GET";
const path = "/api/v1/catalog";
const query = "page=1&limit=20";
const rawBody = "";
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const bodyHash = rawBody ? crypto.createHash("sha256").update(rawBody).digest("hex") : "";
const canonical = [method, path, query, timestamp, nonce, bodyHash].join("\n");
const signature = crypto.createHmac("sha256", apiSecret).update(canonical).digest("hex");

const headers = {
  "X-API-Key": apiKey,
  "X-Timestamp": timestamp,
  "X-Nonce": nonce,
  "X-Signature": signature,
};

const response = await fetch("https://hstore.site/api/v1/catalog?page=1&limit=20", {
  method,
  headers,
});

console.log(await response.json());
import hashlib
import hmac
import json
import secrets
import time
import requests

api_key = "hs_pk_xxxxxxxxxxxxxxxx"
api_secret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
method = "GET"
path = "/api/v1/catalog"
query = "page=1&limit=20"
raw_body = ""
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = hashlib.sha256(raw_body.encode()).hexdigest() if raw_body else ""
canonical = "\n".join([method, path, query, timestamp, nonce, body_hash])
signature = hmac.new(api_secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

headers = {
    "X-API-Key": api_key,
    "X-Timestamp": timestamp,
    "X-Nonce": nonce,
    "X-Signature": signature,
}

response = requests.request(
    method,
    "https://hstore.site/api/v1/catalog?page=1&limit=20",
    headers=headers,
)

print(response.json())
$apiKey = "hs_pk_xxxxxxxxxxxxxxxx"
$apiSecret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$method = "GET"
$path = "/api/v1/catalog"
$query = "page=1&limit=20"
$body = ""
$timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$nonce = [guid]::NewGuid().ToString("N")
$bodyHash = if ($body -ne "") {
    [System.BitConverter]::ToString(([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($body)))).Replace("-", "").ToLower()
} else {
    ""
}
$canonical = "$method`n$path`n$query`n$timestamp`n$nonce`n$bodyHash"
$hmac = [System.Security.Cryptography.HMACSHA256]::new([System.Text.Encoding]::UTF8.GetBytes($apiSecret))
$signature = -join ($hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($canonical)) | ForEach-Object { $_.ToString("x2") })

$headers = @{
    "X-API-Key" = $apiKey
    "X-Timestamp" = "$timestamp"
    "X-Nonce" = $nonce
    "X-Signature" = $signature
}

$params = @{
    Uri = "https://hstore.site/api/v1/catalog?page=1&limit=20"
    Method = $method
    Headers = $headers
}

Invoke-RestMethod @params | ConvertTo-Json -Depth 20
响应示例
{
    "success": true,
    "data": {
        "items": [
            {
                "id": 381,
                "name": "High-Quality Long-Term (Hotmail) Usable after skipping the first 7 days.",
                "slug": "high-quality-long-term-hotmail-usable-after-skipping-the-first-7-days",
                "short_description": "High-Quality Long-Term (Hotmail) Usable after skipping the first 7 days.",
                "price": 0.02,
                "currency": "USD",
                "delivery_type": "auto",
                "stock_available": 7722,
                "product_url": "https://hstore.site/product/381",
                "updated_at": "2026-04-10 15:15:29"
            }
        ],
        "pagination": {
            "page": 1,
            "limit": 20,
            "total": 726,
            "pages": 37
        }
    }
}
获取单个商品
API_KEY="hs_pk_xxxxxxxxxxxxxxxx"
API_SECRET="hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
METHOD="GET"
PATH="/api/v1/products/381"
QUERY=""
TIMESTAMP=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=""
CANONICAL="$METHOD
$PATH
$QUERY
$TIMESTAMP
$NONCE
$BODY_HASH"
SIGNATURE=$(printf "%s" "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed "s/^.* //")

curl -X GET 'https://hstore.site/api/v1/products/381' \
  -H "X-API-Key: $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"
$apiKey = 'hs_pk_xxxxxxxxxxxxxxxx';
$apiSecret = 'hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$method = 'GET';
$path = '/api/v1/products/381';
$query = '';
$body = '';
$timestamp = (string) time();
$nonce = bin2hex(random_bytes(16));
$bodyHash = $body === '' ? '' : hash('sha256', $body);
$canonical = implode("\n", [$method, $path, $query, $timestamp, $nonce, $bodyHash]);
$signature = hash_hmac('sha256', $canonical, $apiSecret);

$headers = [
    'X-API-Key: ' . $apiKey,
    'X-Timestamp: ' . $timestamp,
    'X-Nonce: ' . $nonce,
    'X-Signature: ' . $signature,
];

$ch = curl_init('https://hstore.site/api/v1/products/381');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => $method,
    CURLOPT_HTTPHEADER => $headers,
]);

$response = curl_exec($ch);
curl_close($ch);
echo $response;
import crypto from "node:crypto";

const apiKey = "hs_pk_xxxxxxxxxxxxxxxx";
const apiSecret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const method = "GET";
const path = "/api/v1/products/381";
const query = "";
const rawBody = "";
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const bodyHash = rawBody ? crypto.createHash("sha256").update(rawBody).digest("hex") : "";
const canonical = [method, path, query, timestamp, nonce, bodyHash].join("\n");
const signature = crypto.createHmac("sha256", apiSecret).update(canonical).digest("hex");

const headers = {
  "X-API-Key": apiKey,
  "X-Timestamp": timestamp,
  "X-Nonce": nonce,
  "X-Signature": signature,
};

const response = await fetch("https://hstore.site/api/v1/products/381", {
  method,
  headers,
});

console.log(await response.json());
import hashlib
import hmac
import json
import secrets
import time
import requests

api_key = "hs_pk_xxxxxxxxxxxxxxxx"
api_secret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
method = "GET"
path = "/api/v1/products/381"
query = ""
raw_body = ""
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = hashlib.sha256(raw_body.encode()).hexdigest() if raw_body else ""
canonical = "\n".join([method, path, query, timestamp, nonce, body_hash])
signature = hmac.new(api_secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

headers = {
    "X-API-Key": api_key,
    "X-Timestamp": timestamp,
    "X-Nonce": nonce,
    "X-Signature": signature,
}

response = requests.request(
    method,
    "https://hstore.site/api/v1/products/381",
    headers=headers,
)

print(response.json())
$apiKey = "hs_pk_xxxxxxxxxxxxxxxx"
$apiSecret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$method = "GET"
$path = "/api/v1/products/381"
$query = ""
$body = ""
$timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$nonce = [guid]::NewGuid().ToString("N")
$bodyHash = if ($body -ne "") {
    [System.BitConverter]::ToString(([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($body)))).Replace("-", "").ToLower()
} else {
    ""
}
$canonical = "$method`n$path`n$query`n$timestamp`n$nonce`n$bodyHash"
$hmac = [System.Security.Cryptography.HMACSHA256]::new([System.Text.Encoding]::UTF8.GetBytes($apiSecret))
$signature = -join ($hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($canonical)) | ForEach-Object { $_.ToString("x2") })

$headers = @{
    "X-API-Key" = $apiKey
    "X-Timestamp" = "$timestamp"
    "X-Nonce" = $nonce
    "X-Signature" = $signature
}

$params = @{
    Uri = "https://hstore.site/api/v1/products/381"
    Method = $method
    Headers = $headers
}

Invoke-RestMethod @params | ConvertTo-Json -Depth 20
响应示例
{
    "success": true,
    "data": {
        "id": 381,
        "name": "High-Quality Long-Term (Hotmail) Usable after skipping the first 7 days.",
        "slug": "high-quality-long-term-hotmail-usable-after-skipping-the-first-7-days",
        "short_description": "High-Quality Long-Term (Hotmail) Usable after skipping the first 7 days.",
        "price": 0.02,
        "currency": "USD",
        "delivery_type": "auto",
        "stock_available": 7722,
        "product_url": "https://hstore.site/product/381",
        "updated_at": "2026-04-10 15:15:29",
        "description": "Format includes Hotmail:password. Only supports web login.",
        "price_tiers": [
            {
                "min_quantity": 100,
                "unit_price": 0.019
            },
            {
                "min_quantity": 500,
                "unit_price": 0.018
            }
        ],
        "rules": {
            "delivery_type": "auto",
            "instant_delivery": true,
            "delivery_data_exposed": false
        }
    }
}
读取余额
API_KEY="hs_pk_xxxxxxxxxxxxxxxx"
API_SECRET="hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
METHOD="GET"
PATH="/api/v1/balance"
QUERY=""
TIMESTAMP=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=""
CANONICAL="$METHOD
$PATH
$QUERY
$TIMESTAMP
$NONCE
$BODY_HASH"
SIGNATURE=$(printf "%s" "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed "s/^.* //")

curl -X GET 'https://hstore.site/api/v1/balance' \
  -H "X-API-Key: $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"
$apiKey = 'hs_pk_xxxxxxxxxxxxxxxx';
$apiSecret = 'hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$method = 'GET';
$path = '/api/v1/balance';
$query = '';
$body = '';
$timestamp = (string) time();
$nonce = bin2hex(random_bytes(16));
$bodyHash = $body === '' ? '' : hash('sha256', $body);
$canonical = implode("\n", [$method, $path, $query, $timestamp, $nonce, $bodyHash]);
$signature = hash_hmac('sha256', $canonical, $apiSecret);

$headers = [
    'X-API-Key: ' . $apiKey,
    'X-Timestamp: ' . $timestamp,
    'X-Nonce: ' . $nonce,
    'X-Signature: ' . $signature,
];

$ch = curl_init('https://hstore.site/api/v1/balance');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => $method,
    CURLOPT_HTTPHEADER => $headers,
]);

$response = curl_exec($ch);
curl_close($ch);
echo $response;
import crypto from "node:crypto";

const apiKey = "hs_pk_xxxxxxxxxxxxxxxx";
const apiSecret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const method = "GET";
const path = "/api/v1/balance";
const query = "";
const rawBody = "";
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const bodyHash = rawBody ? crypto.createHash("sha256").update(rawBody).digest("hex") : "";
const canonical = [method, path, query, timestamp, nonce, bodyHash].join("\n");
const signature = crypto.createHmac("sha256", apiSecret).update(canonical).digest("hex");

const headers = {
  "X-API-Key": apiKey,
  "X-Timestamp": timestamp,
  "X-Nonce": nonce,
  "X-Signature": signature,
};

const response = await fetch("https://hstore.site/api/v1/balance", {
  method,
  headers,
});

console.log(await response.json());
import hashlib
import hmac
import json
import secrets
import time
import requests

api_key = "hs_pk_xxxxxxxxxxxxxxxx"
api_secret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
method = "GET"
path = "/api/v1/balance"
query = ""
raw_body = ""
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = hashlib.sha256(raw_body.encode()).hexdigest() if raw_body else ""
canonical = "\n".join([method, path, query, timestamp, nonce, body_hash])
signature = hmac.new(api_secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

headers = {
    "X-API-Key": api_key,
    "X-Timestamp": timestamp,
    "X-Nonce": nonce,
    "X-Signature": signature,
}

response = requests.request(
    method,
    "https://hstore.site/api/v1/balance",
    headers=headers,
)

print(response.json())
$apiKey = "hs_pk_xxxxxxxxxxxxxxxx"
$apiSecret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$method = "GET"
$path = "/api/v1/balance"
$query = ""
$body = ""
$timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$nonce = [guid]::NewGuid().ToString("N")
$bodyHash = if ($body -ne "") {
    [System.BitConverter]::ToString(([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($body)))).Replace("-", "").ToLower()
} else {
    ""
}
$canonical = "$method`n$path`n$query`n$timestamp`n$nonce`n$bodyHash"
$hmac = [System.Security.Cryptography.HMACSHA256]::new([System.Text.Encoding]::UTF8.GetBytes($apiSecret))
$signature = -join ($hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($canonical)) | ForEach-Object { $_.ToString("x2") })

$headers = @{
    "X-API-Key" = $apiKey
    "X-Timestamp" = "$timestamp"
    "X-Nonce" = $nonce
    "X-Signature" = $signature
}

$params = @{
    Uri = "https://hstore.site/api/v1/balance"
    Method = $method
    Headers = $headers
}

Invoke-RestMethod @params | ConvertTo-Json -Depth 20
响应示例
{
    "success": true,
    "data": {
        "balance": 4.887,
        "pending_balance": 0,
        "currency": "USD"
    }
}
创建订单
API_KEY="hs_pk_xxxxxxxxxxxxxxxx"
API_SECRET="hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
METHOD="POST"
PATH="/api/v1/orders"
QUERY=""
BODY='{"product_id":381,"quantity":1,"external_order_id":"shop-order-100045"}'
TIMESTAMP=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=$(printf "%s" "$BODY" | openssl dgst -sha256 -hex | sed "s/^.* //")
CANONICAL="$METHOD
$PATH
$QUERY
$TIMESTAMP
$NONCE
$BODY_HASH"
SIGNATURE=$(printf "%s" "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed "s/^.* //")
IDEMPOTENCY_KEY="idem-shop-order-100045"

curl -X POST 'https://hstore.site/api/v1/orders' \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  --data "$BODY"
$apiKey = 'hs_pk_xxxxxxxxxxxxxxxx';
$apiSecret = 'hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$method = 'POST';
$path = '/api/v1/orders';
$query = '';
$body = '{"product_id":381,"quantity":1,"external_order_id":"shop-order-100045"}';
$timestamp = (string) time();
$nonce = bin2hex(random_bytes(16));
$bodyHash = $body === '' ? '' : hash('sha256', $body);
$canonical = implode("\n", [$method, $path, $query, $timestamp, $nonce, $bodyHash]);
$signature = hash_hmac('sha256', $canonical, $apiSecret);

$headers = [
    'X-API-Key: ' . $apiKey,
    'X-Timestamp: ' . $timestamp,
    'X-Nonce: ' . $nonce,
    'X-Signature: ' . $signature,
    'Content-Type: application/json',
    'Idempotency-Key: idem-shop-order-100045',
];

$ch = curl_init('https://hstore.site/api/v1/orders');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => $method,
    CURLOPT_HTTPHEADER => $headers,
    CURLOPT_POSTFIELDS => $body,
]);

$response = curl_exec($ch);
curl_close($ch);
echo $response;
import crypto from "node:crypto";

const apiKey = "hs_pk_xxxxxxxxxxxxxxxx";
const apiSecret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const method = "POST";
const path = "/api/v1/orders";
const query = "";
const body = {"product_id":381,"quantity":1,"external_order_id":"shop-order-100045"};
const rawBody = JSON.stringify(body);
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const bodyHash = rawBody ? crypto.createHash("sha256").update(rawBody).digest("hex") : "";
const canonical = [method, path, query, timestamp, nonce, bodyHash].join("\n");
const signature = crypto.createHmac("sha256", apiSecret).update(canonical).digest("hex");

const headers = {
  "X-API-Key": apiKey,
  "X-Timestamp": timestamp,
  "X-Nonce": nonce,
  "X-Signature": signature,
  "Content-Type": "application/json",
  "Idempotency-Key": "idem-shop-order-100045",
};

const response = await fetch("https://hstore.site/api/v1/orders", {
  method,
  headers,
  body: rawBody,
});

console.log(await response.json());
import hashlib
import hmac
import json
import secrets
import time
import requests

api_key = "hs_pk_xxxxxxxxxxxxxxxx"
api_secret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
method = "POST"
path = "/api/v1/orders"
query = ""
body = {"product_id":381,"quantity":1,"external_order_id":"shop-order-100045"}
raw_body = json.dumps(body, separators=(",", ":"))
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = hashlib.sha256(raw_body.encode()).hexdigest() if raw_body else ""
canonical = "\n".join([method, path, query, timestamp, nonce, body_hash])
signature = hmac.new(api_secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

headers = {
    "X-API-Key": api_key,
    "X-Timestamp": timestamp,
    "X-Nonce": nonce,
    "X-Signature": signature,
    "Content-Type": "application/json",
    "Idempotency-Key": "idem-shop-order-100045",
}

response = requests.request(
    method,
    "https://hstore.site/api/v1/orders",
    headers=headers,
    data=raw_body,
)

print(response.json())
$apiKey = "hs_pk_xxxxxxxxxxxxxxxx"
$apiSecret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$method = "POST"
$path = "/api/v1/orders"
$query = ""
$body = '{"product_id":381,"quantity":1,"external_order_id":"shop-order-100045"}'
$timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$nonce = [guid]::NewGuid().ToString("N")
$bodyHash = if ($body -ne "") {
    [System.BitConverter]::ToString(([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($body)))).Replace("-", "").ToLower()
} else {
    ""
}
$canonical = "$method`n$path`n$query`n$timestamp`n$nonce`n$bodyHash"
$hmac = [System.Security.Cryptography.HMACSHA256]::new([System.Text.Encoding]::UTF8.GetBytes($apiSecret))
$signature = -join ($hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($canonical)) | ForEach-Object { $_.ToString("x2") })

$headers = @{
    "X-API-Key" = $apiKey
    "X-Timestamp" = "$timestamp"
    "X-Nonce" = $nonce
    "X-Signature" = $signature
    "Idempotency-Key" = "idem-shop-order-100045"
}

$params = @{
    Uri = "https://hstore.site/api/v1/orders"
    Method = $method
    Headers = $headers
    Body = $body
    ContentType = "application/json"
}

Invoke-RestMethod @params | ConvertTo-Json -Depth 20
响应示例
{
    "success": true,
    "data": {
        "id": 16591,
        "order_number": "ORD-CFFD69-16591",
        "external_order_id": "shop-order-100045",
        "status": "delivered",
        "quantity": 1,
        "unit_price": 0.02,
        "total_amount": 0.02,
        "currency": "USD",
        "delivery_type": "auto",
        "delivery": {
            "available": true,
            "items": [
                "[email protected]:password123"
            ]
        },
        "links": {
            "web_url": "https://hstore.site/order/16591",
            "api_url": "https://hstore.site/api/v1/orders/16591"
        }
    }
}
按 external ID 查询订单
API_KEY="hs_pk_xxxxxxxxxxxxxxxx"
API_SECRET="hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
METHOD="GET"
PATH="/api/v1/orders/lookup"
QUERY="external_order_id=shop-order-100045"
TIMESTAMP=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY_HASH=""
CANONICAL="$METHOD
$PATH
$QUERY
$TIMESTAMP
$NONCE
$BODY_HASH"
SIGNATURE=$(printf "%s" "$CANONICAL" | openssl dgst -sha256 -hmac "$API_SECRET" -hex | sed "s/^.* //")

curl -X GET 'https://hstore.site/api/v1/orders/lookup?external_order_id=shop-order-100045' \
  -H "X-API-Key: $API_KEY" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"
$apiKey = 'hs_pk_xxxxxxxxxxxxxxxx';
$apiSecret = 'hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$method = 'GET';
$path = '/api/v1/orders/lookup';
$query = 'external_order_id=shop-order-100045';
$body = '';
$timestamp = (string) time();
$nonce = bin2hex(random_bytes(16));
$bodyHash = $body === '' ? '' : hash('sha256', $body);
$canonical = implode("\n", [$method, $path, $query, $timestamp, $nonce, $bodyHash]);
$signature = hash_hmac('sha256', $canonical, $apiSecret);

$headers = [
    'X-API-Key: ' . $apiKey,
    'X-Timestamp: ' . $timestamp,
    'X-Nonce: ' . $nonce,
    'X-Signature: ' . $signature,
];

$ch = curl_init('https://hstore.site/api/v1/orders/lookup?external_order_id=shop-order-100045');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => $method,
    CURLOPT_HTTPHEADER => $headers,
]);

$response = curl_exec($ch);
curl_close($ch);
echo $response;
import crypto from "node:crypto";

const apiKey = "hs_pk_xxxxxxxxxxxxxxxx";
const apiSecret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const method = "GET";
const path = "/api/v1/orders/lookup";
const query = "external_order_id=shop-order-100045";
const rawBody = "";
const timestamp = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const bodyHash = rawBody ? crypto.createHash("sha256").update(rawBody).digest("hex") : "";
const canonical = [method, path, query, timestamp, nonce, bodyHash].join("\n");
const signature = crypto.createHmac("sha256", apiSecret).update(canonical).digest("hex");

const headers = {
  "X-API-Key": apiKey,
  "X-Timestamp": timestamp,
  "X-Nonce": nonce,
  "X-Signature": signature,
};

const response = await fetch("https://hstore.site/api/v1/orders/lookup?external_order_id=shop-order-100045", {
  method,
  headers,
});

console.log(await response.json());
import hashlib
import hmac
import json
import secrets
import time
import requests

api_key = "hs_pk_xxxxxxxxxxxxxxxx"
api_secret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
method = "GET"
path = "/api/v1/orders/lookup"
query = "external_order_id=shop-order-100045"
raw_body = ""
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = hashlib.sha256(raw_body.encode()).hexdigest() if raw_body else ""
canonical = "\n".join([method, path, query, timestamp, nonce, body_hash])
signature = hmac.new(api_secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

headers = {
    "X-API-Key": api_key,
    "X-Timestamp": timestamp,
    "X-Nonce": nonce,
    "X-Signature": signature,
}

response = requests.request(
    method,
    "https://hstore.site/api/v1/orders/lookup?external_order_id=shop-order-100045",
    headers=headers,
)

print(response.json())
$apiKey = "hs_pk_xxxxxxxxxxxxxxxx"
$apiSecret = "hs_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$method = "GET"
$path = "/api/v1/orders/lookup"
$query = "external_order_id=shop-order-100045"
$body = ""
$timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
$nonce = [guid]::NewGuid().ToString("N")
$bodyHash = if ($body -ne "") {
    [System.BitConverter]::ToString(([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($body)))).Replace("-", "").ToLower()
} else {
    ""
}
$canonical = "$method`n$path`n$query`n$timestamp`n$nonce`n$bodyHash"
$hmac = [System.Security.Cryptography.HMACSHA256]::new([System.Text.Encoding]::UTF8.GetBytes($apiSecret))
$signature = -join ($hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($canonical)) | ForEach-Object { $_.ToString("x2") })

$headers = @{
    "X-API-Key" = $apiKey
    "X-Timestamp" = "$timestamp"
    "X-Nonce" = $nonce
    "X-Signature" = $signature
}

$params = @{
    Uri = "https://hstore.site/api/v1/orders/lookup?external_order_id=shop-order-100045"
    Method = $method
    Headers = $headers
}

Invoke-RestMethod @params | ConvertTo-Json -Depth 20
响应示例
{
    "success": true,
    "data": {
        "id": 16591,
        "order_number": "ORD-CFFD69-16591",
        "external_order_id": "shop-order-100045",
        "status": "delivered",
        "quantity": 1,
        "unit_price": 0.02,
        "total_amount": 0.02,
        "currency": "USD",
        "delivery_type": "auto",
        "delivery": {
            "available": true,
            "items": [
                "[email protected]:password123"
            ]
        },
        "links": {
            "web_url": "https://hstore.site/order/16591",
            "api_url": "https://hstore.site/api/v1/orders/16591"
        }
    }
}
订单安全
  • 每个 POST /orders 都必须同时包含 external_order_id 和 Idempotency-Key。
  • 使用相同 payload 重复提交同一个 idempotency key,会返回同一笔订单响应。
  • 如果用不同 payload 重复使用同一个 key 或 external_order_id,会返回 409。
  • 在不确定是否成功重试后,请用 GET /orders/lookup 对账。

Webhook

Webhook 按密钥分别在 API Keys 页面配置。每个密钥都有自己的 URL、secret、订阅事件和重试队列。

投递请求头
  • X-HStore-Event-Id:稳定的事件 ID。
  • X-HStore-Delivery-Id:唯一的投递尝试 ID。
  • X-HStore-Webhook-Event:事件名称。
  • X-HStore-Webhook-Timestamp:Unix 时间戳。
  • X-HStore-Signature-Version:当前为 v1。
  • X-HStore-Webhook-Signature:小写十六进制 HMAC-SHA256。
投递规则
  • 只允许公共 https:// endpoint。
  • localhost、loopback 和私有 IP 目标会被拒绝。
  • 任意 2xx 响应都会被视为投递成功。
  • 非 2xx 响应和网络失败会自动重试。
  • 你的接收端应以 X-HStore-Delivery-Id 实现幂等处理。
支持的事件
  • product.created - Product became available
  • product.updated - Product details updated
  • product.deleted - Product removed or hidden
  • product.stock_changed - Product stock changed
  • product.price_changed - Product price changed
  • product.tiers_changed - Bulk price tiers changed
  • order.created - Order created
  • order.updated - Order updated
  • order.delivered - Order delivered
  • order.completed - Order completed
  • order.refunded - Order refunded
  • order.disputed - Order disputed
Webhook 载荷示例
{
    "id": "evt_9f2d4f7e5c2a4d2abf6f19cd7ad91234",
    "type": "product.stock_changed",
    "created_at": "2026-04-10T12:30:15Z",
    "livemode": true,
    "api_version": "v1",
    "data": {
        "product": {
            "id": 381,
            "name": "High-Quality Long-Term (Hotmail) Usable after skipping the first 7 days.",
            "price": 0.02,
            "stock_available": 7700
        },
        "previous": {
            "id": 381,
            "stock_available": 7722
        },
        "changes": {
            "stock_available": {
                "before": 7722,
                "after": 7700
            }
        },
        "meta": {
            "source": "purchase"
        }
    }
}
验签规范字符串
TIMESTAMP
DELIVERY_ID
EVENT_ID
EVENT_TYPE
BODY_SHA256
# Webhook signature verification must run inside your app.
# Use this cURL command only to replay a payload into your receiver.
curl -X POST 'https://your-app.example.com/hstore-webhook' \
  -H "Content-Type: application/json" \
  -H "X-HStore-Webhook-Timestamp: 1712750000" \
  -H "X-HStore-Delivery-Id: dlv_xxxxxxxxxxxxxxxx" \
  -H "X-HStore-Event-Id: evt_xxxxxxxxxxxxxxxx" \
  -H "X-HStore-Webhook-Event: product.stock_changed" \
  -H "X-HStore-Webhook-Signature: <hex-hmac-sha256>" \
  --data "{\"id\":\"evt_xxx\",\"type\":\"product.stock_changed\"}"
$webhookSecret = 'whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$timestamp = $_SERVER['HTTP_X_HSTORE_WEBHOOK_TIMESTAMP'] ?? '';
$deliveryId = $_SERVER['HTTP_X_HSTORE_DELIVERY_ID'] ?? '';
$eventId = $_SERVER['HTTP_X_HSTORE_EVENT_ID'] ?? '';
$eventType = $_SERVER['HTTP_X_HSTORE_WEBHOOK_EVENT'] ?? '';
$signature = strtolower($_SERVER['HTTP_X_HSTORE_WEBHOOK_SIGNATURE'] ?? '');
$rawBody = file_get_contents('php://input') ?: '';

$bodyHash = hash('sha256', $rawBody);
$canonical = implode("\n", [$timestamp, $deliveryId, $eventId, $eventType, $bodyHash]);
$expected = hash_hmac('sha256', $canonical, $webhookSecret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Invalid webhook signature');
}
import crypto from "node:crypto";

const webhookSecret = "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const rawBody = await getRawBody(req);
const timestamp = req.get("X-HStore-Webhook-Timestamp") ?? "";
const deliveryId = req.get("X-HStore-Delivery-Id") ?? "";
const eventId = req.get("X-HStore-Event-Id") ?? "";
const eventType = req.get("X-HStore-Webhook-Event") ?? "";
const signature = (req.get("X-HStore-Webhook-Signature") ?? "").toLowerCase();

const bodyHash = crypto.createHash("sha256").update(rawBody).digest("hex");
const canonical = [timestamp, deliveryId, eventId, eventType, bodyHash].join("\n");
const expected = crypto.createHmac("sha256", webhookSecret).update(canonical).digest("hex");

if (expected !== signature) {
  res.status(401).send("Invalid webhook signature");
  return;
}
import hashlib
import hmac

webhook_secret = "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
raw_body = request.get_data(as_text=True)
timestamp = request.headers.get("X-HStore-Webhook-Timestamp", "")
delivery_id = request.headers.get("X-HStore-Delivery-Id", "")
event_id = request.headers.get("X-HStore-Event-Id", "")
event_type = request.headers.get("X-HStore-Webhook-Event", "")
signature = request.headers.get("X-HStore-Webhook-Signature", "").lower()

body_hash = hashlib.sha256(raw_body.encode()).hexdigest()
canonical = "\n".join([timestamp, delivery_id, event_id, event_type, body_hash])
expected = hmac.new(webhook_secret.encode(), canonical.encode(), hashlib.sha256).hexdigest()

if not hmac.compare_digest(expected, signature):
    abort(401, "Invalid webhook signature")
$webhookSecret = "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$timestamp = $Request.Headers["X-HStore-Webhook-Timestamp"]
$deliveryId = $Request.Headers["X-HStore-Delivery-Id"]
$eventId = $Request.Headers["X-HStore-Event-Id"]
$eventType = $Request.Headers["X-HStore-Webhook-Event"]
$signature = ($Request.Headers["X-HStore-Webhook-Signature"] ?? "").ToLower()
$rawBody = $Body

$bodyHash = [System.BitConverter]::ToString(([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($rawBody)))).Replace("-", "").ToLower()
$canonical = "$timestamp`n$deliveryId`n$eventId`n$eventType`n$bodyHash"
$hmac = [System.Security.Cryptography.HMACSHA256]::new([System.Text.Encoding]::UTF8.GetBytes($webhookSecret))
$expected = -join ($hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($canonical)) | ForEach-Object { $_.ToString("x2") })

if ($expected -ne $signature) {
    throw "Invalid webhook signature"
}

错误与限流

{
    "success": false,
    "error": {
        "code": "invalid_signature",
        "message": "签名与规范请求不匹配。",
        "status": 401
    }
}
限流响应头
  • X-RateLimit-Limit:该密钥每分钟允许的最大请求数。
  • X-RateLimit-Remaining:当前窗口内剩余请求数。
  • X-RateLimit-Reset:当前窗口重置时的 Unix 时间戳。
  • Retry-After 只会在 429 时返回。
  • 即使中间层把请求头转成小写,头名称也仍然大小写不敏感。
HTTP 含义 建议操作
200 请求成功。 将返回 payload 视为当前真实状态。
201 订单已创建并返回当前 payload。 保存订单 ID 和交付状态。
202 订单已接受但仍在处理中。 轮询订单接口或等待 webhook。
400 校验失败或请求格式错误。 修正请求后再重试。
401 认证、签名、nonce 或时间戳失败。 重新生成请求头并检查签名逻辑。
402 钱包余额不足。 先给 Main Wallet 充值再重试。
409 重放、请求处理中或 payload 冲突。 仅使用同一组购买标识进行安全重试。
429 已达到限流。 等待到 Retry-After 或 X-RateLimit-Reset 后再继续。
错误代码 HTTP 含义
missing_auth 401 缺少一个或多个必需的认证请求头。
request_expired 401 时间戳过旧,或与服务器时间偏差过大。
invalid_signature 401 规范字符串或 secret 与请求不匹配。
origin_not_allowed 403 Origin 或 Referer 主机与 allowed_domains 不匹配。
missing_idempotency_key 400 POST /orders 请求未携带 Idempotency-Key。
request_in_progress 409 同一笔购买当前正在处理中。
idempotency_conflict 409 同一个 key 或 external_order_id 被用于不同 payload。
insufficient_balance 402 钱包余额不足以完成购买。
product_unavailable 404 商品不存在、未激活,或卖家未激活。
insufficient_stock 409 当前自动发货库存不足。
rate_limited 429 该密钥在当前时间窗口内请求过多。