REST и JSON
Предсказуемые HTTP-методы и единый формат данных.
Создавайте одноразовые и многоразовые платёжные ссылки, получайте подписанные уведомления и сверяйте состояние платежей через единый API.
Предсказуемые HTTP-методы и единый формат данных.
Каждый серверный запрос подтверждается ключом магазина.
События приходят автоматически, status API остаётся для сверки.
Для интеграции понадобятся Merchant ID, Shop ID и секретный ключ магазина из личного кабинета.
Откройте магазин в кабинете и скопируйте идентификаторы и Secret Token.
Подпишите короткоживущий токен алгоритмом HS256 и передайте его в X-Sign.
В ответе вы получите order_id и URL платёжной формы.
Проверьте подпись события и обработайте транзакцию один раз по event_id.
Боевые платежи
https://api.upay.su/v1
Создайте JWT алгоритмом HS256, используя Secret Token магазина. Токен передаётся в каждом защищённом запросе.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...merchantIdintegerИдентификатор мерчанта
timestampintegerВремя создания токена, Unix time
expintegerВремя истечения токена, рекомендуемый TTL — 180 секунд
import time
import jwt
now = int(time.time())
sign = jwt.encode({
"merchantId": 42,
"timestamp": now,
"exp": now + 180,
}, "YOUR_SECRET_TOKEN", algorithm="HS256")
import jwt from "jsonwebtoken";
const now = Math.floor(Date.now() / 1000);
const sign = jwt.sign({
merchantId: 42,
timestamp: now,
exp: now + 180,
}, "YOUR_SECRET_TOKEN", { algorithm: "HS256" });
$now = time();
$sign = JWT::encode([
'merchantId' => 42,
'timestamp' => $now,
'exp' => $now + 180,
], 'YOUR_SECRET_TOKEN', 'HS256');
/order/createСоздаёт платёжную ссылку. Сохраните полученный order_id у себя — он понадобится для сверки статуса.
shop_id *integerИдентификатор магазина
amount *numberСумма заказа
currencystringВалюта По умолчанию: RUB
type_orderenumONETIME или MULTI По умолчанию: ONETIME
merchant_order_idstringID заказа в вашей системе
webhook_urlstringHTTPS URL для уведомлений
return_urlstringКуда вернуть плательщика
expired_atdatetimeСрок действия одноразовой ссылки По умолчанию: +24 часа
is_arbitrary_amount_allowedbooleanРазрешить плательщику менять сумму По умолчанию: false
curl -X POST \
https://api.upay.su/v1/order/create \
-H "Content-Type: application/json" \
-H "X-Sign: $SIGN" \
-d '{
"shop_id": 7,
"amount": 1500,
"currency": "RUB",
"type_order": "ONETIME",
"description": "Заказ #1001",
"merchant_order_id": "1001",
"webhook_url": "https://merchant.example/webhooks/upay"
}'
import requests
response = requests.post(
"https://api.upay.su/v1/order/create",
headers={"X-Sign": sign},
json={
"shop_id": 7,
"amount": 1500,
"currency": "RUB",
"type_order": "ONETIME",
"merchant_order_id": "1001",
"webhook_url": "https://merchant.example/webhooks/upay",
},
)
order = response.json()
const response = await fetch(
"https://api.upay.su/v1/order/create",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Sign": sign,
},
body: JSON.stringify({
shop_id: 7,
amount: 1500,
currency: "RUB",
type_order: "ONETIME",
merchant_order_id: "1001",
webhook_url: "https://merchant.example/webhooks/upay",
}),
},
);
const order = await response.json();
$curl = curl_init('https://api.upay.su/v1/order/create');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Sign: ' . $sign,
],
CURLOPT_POSTFIELDS => json_encode([
'shop_id' => 7,
'amount' => 1500,
'currency' => 'RUB',
'type_order' => 'ONETIME',
'merchant_order_id' => '1001',
]),
]);
$order = json_decode(curl_exec($curl), true);
{
"order_id": "ord_123",
"merchant_order_id": "1001",
"amount": 1500,
"currency": "RUB",
"type_order": "ONETIME",
"status": "OPEN",
"payment_url": "https://upay.su/pay/ord_123",
"expired_at": "2026-08-31T12:00:00Z"
}
После оплаты заказ становится COMPLETED. Неудачная попытка не закрывает ссылку, пока она не истекла.
Ссылка остаётся ACTIVE после оплаты. Каждая оплата создаёт новую транзакцию с уникальным ID.
/order/status/v2Возвращает единый объект заказа и список его транзакций. Передайте ровно один идентификатор: order_id или merchant_order_id.
shop_id *integerИдентификатор магазина
order_idstringИдентификатор UPay
merchant_order_idstringИдентификатор в системе мерчанта
transactions_limitintegerОт 1 до 100 По умолчанию: 20
transactions_cursorstringКурсор следующей страницы
curl "https://api.upay.su/v1/order/status/v2?shop_id=7&order_id=ord_123&transactions_limit=20" \
-H "X-Sign: $SIGN"
response = requests.get(
"https://api.upay.su/v1/order/status/v2",
headers={"X-Sign": sign},
params={
"shop_id": 7,
"order_id": "ord_123",
"transactions_limit": 20,
},
)
result = response.json()
const query = new URLSearchParams({
shop_id: "7",
order_id: "ord_123",
transactions_limit: "20",
});
const response = await fetch(
`https://api.upay.su/v1/order/status/v2?${query}`,
{ headers: { "X-Sign": sign } },
);
const result = await response.json();
$query = http_build_query([
'shop_id' => 7,
'order_id' => 'ord_123',
'transactions_limit' => 20,
]);
$curl = curl_init('https://api.upay.su/v1/order/status/v2?' . $query);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-Sign: ' . $sign],
]);
$result = json_decode(curl_exec($curl), true);
{
"success": true,
"data": {
"order_id": "ord_123",
"merchant_order_id": "1001",
"type_order": "ONETIME",
"order_status": "COMPLETED",
"payment_status": "PAID",
"amount": 1500,
"currency": "RUB",
"payment_url": "https://upay.su/pay/ord_123",
"created_at": "2026-08-30T12:00:00Z",
"expired_at": "2026-08-31T12:00:00Z",
"transactions": {
"items": [
{
"transaction_id": "tx_789",
"status": "PAID",
"amount": 1500,
"currency": "RUB",
"created_at": "2026-08-30T12:05:00Z"
}
],
"total": 1,
"has_more": false,
"next_cursor": null
}
}
}
Транзакции отсортированы от новых к старым. Если has_more: true, передайте полученный next_cursor в следующем запросе.
Первый запрос→next_cursor→Следующая страницаGET /order/status/v2
?shop_id=7
&order_id=ord_123
&transactions_limit=20
&transactions_cursor=tx_789
Apple Gift API доступен магазинам, для которых подключены цифровые товары. Сначала получите актуальный список доступных номиналов и сохраните id выбранного продукта.
/order/digital_goods/apple_giftsshop_id *integerМагазин с подключёнными цифровыми товарами
curl -X GET \
https://api.upay.su/v1/order/digital_goods/apple_gifts \
-H "Content-Type: application/json" \
-H "X-Sign: $SIGN" \
-d '{"shop_id": 7}'
products = requests.get(
"https://api.upay.su/v1/order/digital_goods/apple_gifts",
headers={"X-Sign": sign},
json={"shop_id": 7},
).json()["data"]
const response = await fetch(
"https://api.upay.su/v1/order/digital_goods/apple_gifts",
{
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Sign": sign,
},
body: JSON.stringify({ shop_id: 7 }),
},
);
const { data: products } = await response.json();
{
"data": [
{
"id": 1482,
"name": "Apple Wallet Code 1000 RUB",
"price": 1122.7,
"currency": "RUB",
"quantity": 24,
"account_region": "Russia"
}
]
}
/order/digital_goods/apple_gifts/{product_id}Создаёт одноразовый заказ на выбранный номинал. Значение amount не должно быть меньше актуальной цены продукта из предыдущего запроса.
product_id *integerID номинала в URL
shop_id *integerИдентификатор магазина
amount *numberСумма заказа, не ниже цены продукта
currency *stringВалюта заказа
email *stringEmail получателя Apple Gift
return_url *stringURL возврата после оплаты
webhook_url *stringURL уведомления об оплате
descriptionstringОписание заказа По умолчанию: null
merchant_order_idstringID в системе мерчанта По умолчанию: null
curl -X POST \
https://api.upay.su/v1/order/digital_goods/apple_gifts/1482 \
-H "Content-Type: application/json" \
-H "X-Sign: $SIGN" \
-d '{
"shop_id": 7,
"amount": 1122.70,
"currency": "RUB",
"email": "customer@example.com",
"return_url": "https://merchant.example/success",
"webhook_url": "https://merchant.example/webhooks/upay",
"merchant_order_id": "gift-1001"
}'
gift = requests.post(
"https://api.upay.su/v1/order/digital_goods/apple_gifts/1482",
headers={"X-Sign": sign},
json={
"shop_id": 7,
"amount": 1122.70,
"currency": "RUB",
"email": "customer@example.com",
"return_url": "https://merchant.example/success",
"webhook_url": "https://merchant.example/webhooks/upay",
"merchant_order_id": "gift-1001",
},
).json()
const response = await fetch(
"https://api.upay.su/v1/order/digital_goods/apple_gifts/1482",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Sign": sign,
},
body: JSON.stringify({
shop_id: 7,
amount: 1122.70,
currency: "RUB",
email: "customer@example.com",
return_url: "https://merchant.example/success",
webhook_url: "https://merchant.example/webhooks/upay",
merchant_order_id: "gift-1001",
}),
},
);
const gift = await response.json();
$payload = [
'shop_id' => 7,
'amount' => 1122.70,
'currency' => 'RUB',
'email' => 'customer@example.com',
'return_url' => 'https://merchant.example/success',
'webhook_url' => 'https://merchant.example/webhooks/upay',
'merchant_order_id' => 'gift-1001',
];
$curl = curl_init('https://api.upay.su/v1/order/digital_goods/apple_gifts/1482');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-Sign: ' . $sign],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$gift = json_decode(curl_exec($curl), true);
{
"order_id": "gift_123",
"amount": 1122.7,
"currency": "RUB",
"type_gift": "apple",
"status": "waiting",
"payment_url": "https://upay.su/pay/ord_456",
"merchant_order_id": "gift-1001",
"expired_at": "2026-08-31T12:00:00Z"
}
/order/status/digital_goods/apple_gifts/Передайте shop_id и ровно один идентификатор. После подготовки подарка поле coupon_code будет содержать код.
shop_id *integerИдентификатор магазина
order_idstringID заказа Apple Gift
merchant_order_idstringID заказа в системе мерчанта
curl -X GET \
https://api.upay.su/v1/order/status/digital_goods/apple_gifts/ \
-H "Content-Type: application/json" \
-H "X-Sign: $SIGN" \
-d '{"shop_id": 7, "order_id": "gift_123"}'
gift = requests.get(
"https://api.upay.su/v1/order/status/digital_goods/apple_gifts/",
headers={"X-Sign": sign},
json={"shop_id": 7, "order_id": "gift_123"},
).json()
const response = await fetch(
"https://api.upay.su/v1/order/status/digital_goods/apple_gifts/",
{
method: "GET",
headers: { "Content-Type": "application/json", "X-Sign": sign },
body: JSON.stringify({ shop_id: 7, order_id: "gift_123" }),
},
);
const gift = await response.json();
{
"order_id": "gift_123",
"amount": 1122.7,
"payment_url": "https://upay.su/pay/ord_456",
"type_gift": "apple",
"merchant_order_id": "gift-1001",
"status": "ready",
"coupon_code": "XXXX-XXXX-XXXX",
"product_id": 1482
}
waitingОжидается оплата.
readyКод подготовлен и доступен в coupon_code.
completedВыдача завершена.
failedНе удалось подготовить подарок.
UPay отправляет событие payment.succeeded на webhook_url заказа. Отвечайте любым HTTP-кодом 2xx только после успешного сохранения события.
X-UPay-Signature: <JWT>X-UPay-Event-Id: evt_123{
"event": "payment.succeeded",
"event_id": "evt_123",
"transaction_id": "tx_789",
"order_id": "ord_123",
"merchant_order_id": "1001",
"status": "PAID",
"amount": 1500,
"currency": "RUB",
"shop_id": 7,
"commission": 0.03,
"terminal_name": "SBP"
}
Подпись создаётся алгоритмом HS256 с Secret Token того магазина, которому принадлежит заказ. Проверяйте токен до обработки тела.
Прочитайте точные байты тела запроса до JSON-декодирования.
Проверьте JWT из X-UPay-Signature ключом магазина и разрешите только HS256.
Вычислите SHA-256 тела и сравните с claim payload_sha256.
Сравните event_id, transaction_id, order_id и shop_id между JWT и body.
Если event_id уже обработан, верните 200 без повторной выдачи товара.
import hashlib
import hmac
import jwt
raw_body = await request.body()
token = request.headers["X-UPay-Signature"]
claims = jwt.decode(
token,
"YOUR_SECRET_TOKEN",
algorithms=["HS256"],
)
actual_hash = hashlib.sha256(raw_body).hexdigest()
if not hmac.compare_digest(actual_hash, claims["payload_sha256"]):
raise ValueError("Webhook payload was changed")
event = await request.json()
assert event["event_id"] == claims["event_id"]
assert event["transaction_id"] == claims["transaction_id"]
import crypto from "node:crypto";
import jwt from "jsonwebtoken";
const token = req.header("X-UPay-Signature");
const claims = jwt.verify(token, SECRET_TOKEN, {
algorithms: ["HS256"],
});
const actualHash = crypto
.createHash("sha256")
.update(req.rawBody)
.digest("hex");
if (actualHash !== claims.payload_sha256) {
throw new Error("Webhook payload was changed");
}
При тайм-ауте или ответе не из диапазона 2xx UPay повторит отправку с увеличивающимся интервалом. Один webhook сохраняет одинаковый event_id во всех попытках.
Событие считается доставленным.
Событие ставится в очередь повторно.
UPay повторит запрос позднее.
ACTIVEСсылка доступна для оплаты.
COMPLETEDОдноразовый заказ успешно оплачен.
EXPIREDСрок действия одноразовой ссылки истёк.
PROCESSINGПлатёж обрабатывается.
PAIDПлатёж подтверждён.
DECLINEDПлатёж отклонён.
Новый status API возвращает стабильный машиночитаемый формат ошибки.
{
"success": false,
"error": {
"code": "ORDER_NOT_FOUND",
"message": "Order not found"
}
}
INVALID_ORDER_IDENTIFIERНе передан идентификатор или переданы оба сразу.
INVALID_TRANSACTIONS_LIMITЛимит находится вне диапазона 1–100.
INVALID_CURSORКурсор не принадлежит этому заказу.
INVALID_SIGNATUREJWT отсутствует, истёк или подписан неверным ключом.
ORDER_NOT_FOUNDЗаказ не найден в указанном магазине.
MERCHANT_ORDER_ID_NOT_UNIQUEНайдено несколько заказов; используйте order_id.
Начните вводить название раздела