Рабочая документация API
Документация API HStore
Base URL
https://hstore.site/api/v1
Версияv1
Лимит по умолчанию60/min
Авторизация
HMAC-SHA256
Окно timestamp
5 минут
Идемпотентность
24 часа
Попытки webhook
До 6
Быстрый старт
- Создайте отдельный API-ключ для каждой интеграции на странице API Keys.
- Подписывайте запросы только на сервере. Никогда не раскрывайте secret во frontend-коде.
- Сначала читайте /catalog, обновляйте выбранный товар через /products/{id}, затем создавайте заказ.
- Всегда отправляйте стабильные external_order_id и Idempotency-Key в POST /orders.
- Используйте /orders/{id} или /orders/lookup для сверки после повторных попыток или таймаутов.
Аутентификация и модель запроса
| Заголовок | Обязательно | Описание |
|---|---|---|
X-API-Key |
Да | Публичный идентификатор ключа. |
X-Timestamp |
Да | Unix timestamp в секундах. Должен укладываться в 5 минут. |
X-Nonce |
Да | Уникальная случайная строка, используемая один раз для защиты от replay. |
X-Signature |
Да | Lowercase hex HMAC-SHA256 от канонического запроса. |
Idempotency-Key |
POST /orders | Обязателен при создании заказа. Используйте тот же ключ при безопаском повторе той же покупки. |
Origin / Referer |
Только если настроены разрешенные домены | Должен в точности совпадать с одним из настроенных доменов, если включено ограничение по доменам. |
Каноническая строка
METHOD
PATH
QUERY
TIMESTAMP
NONCE
BODY_HASH
Правила
- PATH должен в точности совпадать с путем запроса, включая /api/v1.
- QUERY — это исходная строка запроса без ?, в том же порядке, в котором вы ее отправляете.
- BODY_HASH пустой для GET и равен SHA256 от точного raw JSON body для POST.
- X-Signature должен быть в виде lowercase hex HMAC-SHA256.
- Если настроены разрешенные домены, Origin или Referer должны совпадать точно.
Эндпоинты
| Метод | Путь | Описание |
|---|---|---|
GET |
/api/v1/ |
Метаданные API и список поддерживаемых webhook-событий. |
GET |
/api/v1/catalog |
Список активных товаров с пагинацией и фильтрами. |
GET |
/api/v1/products/{id} |
Полные данные товара перед checkout. |
GET |
/api/v1/balance |
Текущий баланс кошелька и pending balance. |
POST |
/api/v1/orders |
Создать и списать заказ через Main Wallet. |
GET |
/api/v1/orders/{id} |
Получить заказ по внутреннему ID HStore. |
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.
- Повторное использование одного и того же idempotency key с тем же payload возвращает тот же ответ по заказу.
- Повторное использование того же ключа или external_order_id с другим payload возвращает 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 timestamp.
- X-HStore-Signature-Version — сейчас v1.
- X-HStore-Webhook-Signature — lowercase hex HMAC-SHA256.
Правила доставки
- Разрешены только публичные endpoints по https://.
- Локальные адреса, loopback и private IP отклоняются.
- Любой ответ 2xx помечает доставку как успешную.
- Не-2xx ответы и сетевые ошибки автоматически повторяются.
- Ваш receiver должен быть идемпотентным по X-HStore-Delivery-Id.
Поддерживаемые события
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
Пример payload 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 timestamp, когда текущее окно сбросится.
- Retry-After возвращается только на 429.
- Имена заголовков нечувствительны к регистру, даже если посредник приводит их к нижнему регистру.
| HTTP | Значение | Рекомендуемое действие |
|---|---|---|
200 |
Запрос выполнен успешно. | Используйте payload как актуальную истину. |
201 |
Заказ создан и текущий payload возвращен. | Сохраните order ID и состояние доставки. |
202 |
Заказ принят, но все еще обрабатывается. | Опросите endpoint заказа или дождитесь webhook. |
400 |
Ошибка валидации или формата запроса. | Исправьте запрос перед повтором. |
401 |
Ошибка auth, signature, nonce или timestamp. | Сгенерируйте заголовки заново и проверьте логику подписи. |
402 |
Недостаточно средств в кошельке. | Пополните Main Wallet перед повтором. |
409 |
Replay, запрос в процессе или конфликт payload. | Повторяйте безопасно, используя те же идентификаторы покупки. |
429 |
Достигнут лимит запросов. | Сделайте паузу до Retry-After или X-RateLimit-Reset. |
| Код ошибки | HTTP | Значение |
|---|---|---|
missing_auth |
401 |
Отсутствует один или несколько обязательных auth-заголовков. |
request_expired |
401 |
Timestamp слишком старый или слишком далек от времени сервера. |
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 |
Тот же ключ или external_order_id был повторно использован с другим payload. |
insufficient_balance |
402 |
Баланса кошелька недостаточно для покупки. |
product_unavailable |
404 |
Товар неактивен, отсутствует или продавец неактивен. |
insufficient_stock |
409 |
Для auto-delivery сейчас недостаточно stock. |
rate_limited |
429 |
Слишком много запросов для этого ключа в текущем окне. |