Production API Documentation
HStore API Docs
Base URL
https://hstore.site/api/v1
Versionv1
Default Limit60/min
Auth
HMAC-SHA256
Timestamp Window
5 minutes
Idempotency
24 hours
Webhook Attempts
Up to 6
Quick Start
- Create one API key per integration from API Keys.
- Keep signing on the server only. Never expose the secret in frontend code.
- Read /catalog, refresh the selected product with /products/{id}, then create the order.
- Always send a stable external_order_id and Idempotency-Key on POST /orders.
- Use /orders/{id} or /orders/lookup to reconcile after retries or timeouts.
Authentication and Request Model
| Header | Required | Description |
|---|---|---|
X-API-Key |
Yes | Public key identifier. |
X-Timestamp |
Yes | Unix timestamp in seconds. Must be within 5 minutes. |
X-Nonce |
Yes | Unique random string, used once per key to block replay. |
X-Signature |
Yes | Lowercase hex HMAC-SHA256 over the canonical request. |
Idempotency-Key |
POST /orders | Required on order creation. Reuse it when retrying the same purchase. |
Origin / Referer |
Only if allowed domains are configured | Must match one configured domain exactly when domain restriction is enabled. |
Canonical String
METHOD
PATH
QUERY
TIMESTAMP
NONCE
BODY_HASH
Rules
- PATH must match the exact request path, including /api/v1.
- QUERY is the raw query string without ?, in the same order you send.
- BODY_HASH is empty for GET and SHA256 of the exact raw JSON body for POST.
- X-Signature must be lowercase hex HMAC-SHA256.
- If allowed domains are configured, Origin or Referer must match exactly.
Endpoints
| Method | Path | Description |
|---|---|---|
GET |
/api/v1/ |
API metadata and supported webhook events. |
GET |
/api/v1/catalog |
List active products with pagination and filters. |
GET |
/api/v1/products/{id} |
Get full product details before checkout. |
GET |
/api/v1/balance |
Read current wallet balance and pending balance. |
POST |
/api/v1/orders |
Create and charge an order using Main Wallet. |
GET |
/api/v1/orders/{id} |
Read an order by HStore order ID. |
GET |
/api/v1/orders/lookup?external_order_id=... |
Recover an order using your own external reference. |
Examples
API metadata
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 20Example response
{
"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"
]
}
}
}
List catalog
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 20Example response
{
"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
}
}
}
Get one product
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 20Example response
{
"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
}
}
}
Read balance
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 20Example response
{
"success": true,
"data": {
"balance": 4.887,
"pending_balance": 0,
"currency": "USD"
}
}
Create order
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 20Example response
{
"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"
}
}
}
Lookup order by 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 20Example response
{
"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"
}
}
}
Order Safety
- Every POST /orders must include both external_order_id and Idempotency-Key.
- Reusing the same idempotency key with the same payload returns the same order response.
- Reusing the same key or external order ID with a different payload returns 409.
- After an uncertain retry, reconcile with GET /orders/lookup.
Webhooks
Webhooks are configured per key from API Keys. Each key has its own URL, secret, subscribed events, and retry queue.
Delivery headers
- X-HStore-Event-Id stable event ID.
- X-HStore-Delivery-Id unique delivery attempt ID.
- X-HStore-Webhook-Event event name.
- X-HStore-Webhook-Timestamp Unix timestamp.
- X-HStore-Signature-Version currently v1.
- X-HStore-Webhook-Signature lowercase hex HMAC-SHA256.
Delivery rules
- Only public https:// endpoints are allowed.
- Localhost, loopback, and private IP destinations are rejected.
- Any 2xx response marks a delivery successful.
- Non-2xx responses and network failures are retried automatically.
- Your receiver should be idempotent by X-HStore-Delivery-Id.
Supported events
product.created- Product became availableproduct.updated- Product details updatedproduct.deleted- Product removed or hiddenproduct.stock_changed- Product stock changedproduct.price_changed- Product price changedproduct.tiers_changed- Bulk price tiers changedorder.created- Order createdorder.updated- Order updatedorder.delivered- Order deliveredorder.completed- Order completedorder.refunded- Order refundedorder.disputed- Order disputed
Example webhook payload
{
"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"
}
}
}Verification canonical string
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"
}Errors and Rate Limits
{
"success": false,
"error": {
"code": "invalid_signature",
"message": "Signature does not match the canonical request.",
"status": 401
}
}Rate limit headers
- X-RateLimit-Limit maximum requests per minute for the key.
- X-RateLimit-Remaining remaining requests in the current window.
- X-RateLimit-Reset Unix timestamp when the current window resets.
- Retry-After is returned only on 429.
- Header names are case-insensitive even if an intermediary lowercases them.
| HTTP | Meaning | Recommended action |
|---|---|---|
200 |
Request succeeded. | Use the payload as current truth. |
201 |
Order created and current payload returned. | Persist order IDs and delivery state. |
202 |
Order accepted but still processing. | Poll the order endpoint or wait for a webhook. |
400 |
Validation or request format error. | Fix the request before retrying. |
401 |
Auth, signature, nonce, or timestamp failure. | Regenerate headers and verify signing logic. |
402 |
Insufficient wallet balance. | Top up Main Wallet before retrying. |
409 |
Replay, request in progress, or payload conflict. | Retry safely using the same purchase identifiers only. |
429 |
Rate limit reached. | Back off until Retry-After or X-RateLimit-Reset. |
| Error code | HTTP | Meaning |
|---|---|---|
missing_auth |
401 |
One or more required auth headers are missing. |
request_expired |
401 |
Timestamp is too old or too far from server time. |
invalid_signature |
401 |
The canonical string or secret does not match the request. |
origin_not_allowed |
403 |
Origin or Referer host did not match allowed_domains. |
missing_idempotency_key |
400 |
POST /orders was sent without Idempotency-Key. |
request_in_progress |
409 |
The same purchase is already being processed. |
idempotency_conflict |
409 |
The same key or external_order_id was reused with a different payload. |
insufficient_balance |
402 |
Wallet balance is too low for the purchase. |
product_unavailable |
404 |
Product is inactive, missing, or seller is not active. |
insufficient_stock |
409 |
Auto-delivery stock is not sufficient right now. |
rate_limited |
429 |
Too many requests for this key in the current window. |