6. Подпись запросов
Каждый запрос к API подписывается секретом ключа (HMAC-SHA256). Подпись делается на вашем сервере. Секрет нельзя встраивать в браузер, мобильное приложение или URL.
6. Подпись запросов
Каждый запрос к API подписывается секретом ключа (HMAC-SHA256). Подпись делается **на вашем сервере**. Секрет нельзя встраивать в браузер, мобильное приложение или URL.
Заголовки
| Заголовок | Значение | | --- | --- | | `X-Project-Id` | ID магазина из кабинета. | | `X-Key-Id` | ID активного API-ключа этого магазина. | | `X-Timestamp` | Unix-время в секундах (10 цифр). Допуск ±300 секунд. | | `X-Nonce` | Новая случайная строка для каждой попытки: 16–128 символов `A–Z a–z 0–9 _ -`. | | `X-Signature` | `v1=` и hex-подпись HMAC-SHA256. | | `Idempotency-Key` | Только для изменяющих `POST`: 8–128 символов `A–Z a–z 0–9 _ . : -`. | | `Content-Type` | Для `POST`: `application/json` (UTF-8). |
Что подписывать
Строка из пяти частей, разделённых символом перевода строки `\n`:
METHOD
/api/v1/path?query=value
TIMESTAMP
NONCE
SHA256_HEX_ОТ_ТОЧНЫХ_БАЙТОВ_ТЕЛА`METHOD` — в верхнем регистре (`GET`, `POST`).
Путь начинается с `/api/v1/` и **включает query string**, как она отправляется.
Для `GET` тело пустое: хешируется пустая строка.
Подписывается ровно та последовательность байтов, которую вы отправляете. Не меняйте пробелы и порядок полей после подписи.
Подпись: `hex(HMAC_SHA256(secret, строка))`, в заголовок: `v1=<hex>`.
Пример (Node.js)
import { createHash, createHmac, randomBytes } from "node:crypto";
const API = "https://api.shiftpay.cc";
const { SHIFTPAY_PROJECT_ID: projectId, SHIFTPAY_KEY_ID: keyId, SHIFTPAY_API_SECRET: secret } = process.env;
export async function shiftpay(method, path, bodyObject, idempotencyKey) {
const body = bodyObject === undefined ? "" : JSON.stringify(bodyObject);
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = randomBytes(24).toString("hex");
const digest = createHash("sha256").update(body).digest("hex");
const canonical = [method, path, timestamp, nonce, digest].join("\n");
const signature = createHmac("sha256", secret).update(canonical).digest("hex");
const headers = {
"X-Project-Id": projectId, "X-Key-Id": keyId, "X-Timestamp": timestamp,
"X-Nonce": nonce, "X-Signature": "v1=" + signature,
};
if (method === "POST") {
headers["Content-Type"] = "application/json";
headers["Idempotency-Key"] = idempotencyKey; // обязателен для POST
}
const response = await fetch(API + path, {
method, headers, body: method === "POST" ? body : undefined,
redirect: "error", signal: AbortSignal.timeout(15000),
});
const data = await response.json();
if (!response.ok) throw Object.assign(new Error(data.code), { status: response.status, problem: data });
return data;
}
// Пример: список платежей
// const page = await shiftpay("GET", "/api/v1/payments?limit=20");Пример (Python)
import hashlib, hmac, json, os, secrets, time
import requests
API = "https://api.shiftpay.cc"
PROJECT_ID = os.environ["SHIFTPAY_PROJECT_ID"]
KEY_ID = os.environ["SHIFTPAY_KEY_ID"]
SECRET = os.environ["SHIFTPAY_API_SECRET"].encode()
def shiftpay(method, path, body=None, idempotency_key=None):
raw = b"" if body is None else json.dumps(body, separators=(",", ":"), ensure_ascii=False).encode()
timestamp = str(int(time.time()))
nonce = secrets.token_hex(24)
canonical = "\n".join([method, path, timestamp, nonce, hashlib.sha256(raw).hexdigest()])
signature = hmac.new(SECRET, canonical.encode(), hashlib.sha256).hexdigest()
headers = {"X-Project-Id": PROJECT_ID, "X-Key-Id": KEY_ID, "X-Timestamp": timestamp,
"X-Nonce": nonce, "X-Signature": "v1=" + signature}
if method == "POST":
headers["Content-Type"] = "application/json"
headers["Idempotency-Key"] = idempotency_key
response = requests.request(method, API + path, headers=headers, data=raw or None, timeout=15, allow_redirects=False)
data = response.json()
if not response.ok:
raise RuntimeError(f"{response.status_code}: {data.get('code')}")
return dataВажно для Python: отправляйте именно `raw` (те же байты, которые хешировали), а не позволяйте библиотеке пересериализовать JSON.
Если подпись не проходит
`401 expired_signature` — часы сервера уходят больше чем на 5 минут. Синхронизируйте время (NTP).
`401 invalid_signature` — подписаны не те байты, другой путь/query или неверный секрет.
`401 nonce_reused` — nonce уже использован. Для повтора нужен **новый nonce и новая подпись**, но не новый `Idempotency-Key`.
`401 invalid_authentication` — неверный `X-Project-Id`, `X-Key-Id`, формат заголовков, ключ отозван.
`429 rate_limit` — больше 180 запросов в минуту на ключ. Учитывайте заголовок `Retry-After`.
---
Страница была полезна?
