Рабочая документация API

Документация API HStore

Base URLhttps://hstore.site/api/v1 Версияv1 Лимит по умолчанию60/min
Авторизация
HMAC-SHA256
Окно timestamp
5 минут
Идемпотентность
24 часа
Попытки webhook
До 6

Быстрый старт

  1. Создайте отдельный API-ключ для каждой интеграции на странице API Keys.
  2. Подписывайте запросы только на сервере. Никогда не раскрывайте secret во frontend-коде.
  3. Сначала читайте /catalog, обновляйте выбранный товар через /products/{id}, затем создавайте заказ.
  4. Всегда отправляйте стабильные external_order_id и Idempotency-Key в POST /orders.
  5. Используйте /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 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
Пример 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 Слишком много запросов для этого ключа в текущем окне.