API Docs
UPay APIv1

Принимайте платежи
без лишней сложности

Создавайте одноразовые и многоразовые платёжные ссылки, получайте подписанные уведомления и сверяйте состояние платежей через единый API.

01

REST и JSON

Предсказуемые HTTP-методы и единый формат данных.

02

JWT-подпись

Каждый серверный запрос подтверждается ключом магазина.

03

Webhook first

События приходят автоматически, status API остаётся для сверки.

Начало работы

Первый платёж за четыре шага

Для интеграции понадобятся Merchant ID, Shop ID и секретный ключ магазина из личного кабинета.

1

Получите реквизиты API

Откройте магазин в кабинете и скопируйте идентификаторы и Secret Token.

2

Создайте JWT

Подпишите короткоживущий токен алгоритмом HS256 и передайте его в X-Sign.

3

Создайте заказ

В ответе вы получите order_id и URL платёжной формы.

4

Примите webhook

Проверьте подпись события и обработайте транзакцию один раз по event_id.

Подключение

Production API

Production

Боевые платежи

https://api.upay.su/v1
Безопасность

JWT-аутентификация

Создайте JWT алгоритмом HS256, используя Secret Token магазина. Токен передаётся в каждом защищённом запросе.

X-SigneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Payload токена

ПолеТипОписание
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');
Платёжные ссылки

Создание заказа

POST/order/create

Создаёт платёжную ссылку. Сохраните полученный order_id у себя — он понадобится для сверки статуса.

ПолеТипОписание
shop_id *integer

Идентификатор магазина

amount *number

Сумма заказа

currencystring

Валюта По умолчанию: RUB

type_orderenum

ONETIME или MULTI По умолчанию: ONETIME

merchant_order_idstring

ID заказа в вашей системе

webhook_urlstring

HTTPS 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);
Ответ · 201
{
  "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"
}
Модель данных

Одноразовые и многоразовые ссылки

ONETIME

Один успешный платёж

После оплаты заказ становится COMPLETED. Неудачная попытка не закрывает ссылку, пока она не истекла.

  • Интернет-магазины
  • Счета и разовые услуги
  • Заказы с фиксированной суммой
MULTI

Неограниченное число платежей

Ссылка остаётся ACTIVE после оплаты. Каждая оплата создаёт новую транзакцию с уникальным ID.

  • Донаты и сборы
  • Постоянная ссылка на оплату
  • Повторные продажи
Платёжные ссылки

Получение статуса заказа

GET/order/status/v2

Возвращает единый объект заказа и список его транзакций. Передайте ровно один идентификатор: order_id или merchant_order_id.

Query-параметрТипОписание
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);
Ответ200 OK
application/json
{
  "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

Apple Gift API доступен магазинам, для которых подключены цифровые товары. Сначала получите актуальный список доступных номиналов и сохраните id выбранного продукта.

GET/order/digital_goods/apple_gifts
Поле bodyТипОписание
shop_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();
Ответ · 200
{
  "data": [
    {
      "id": 1482,
      "name": "Apple Wallet Code 1000 RUB",
      "price": 1122.7,
      "currency": "RUB",
      "quantity": 24,
      "account_region": "Russia"
    }
  ]
}
Цифровые товары

Создание заказа Apple Gift

POST/order/digital_goods/apple_gifts/{product_id}

Создаёт одноразовый заказ на выбранный номинал. Значение amount не должно быть меньше актуальной цены продукта из предыдущего запроса.

ПолеТипОписание
product_id *integer

ID номинала в URL

shop_id *integer

Идентификатор магазина

amount *number

Сумма заказа, не ниже цены продукта

currency *string

Валюта заказа

email *string

Email получателя Apple Gift

return_url *string

URL возврата после оплаты

webhook_url *string

URL уведомления об оплате

descriptionstring

Описание заказа По умолчанию: null

merchant_order_idstring

ID в системе мерчанта По умолчанию: 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);
Ответ · 201
{
  "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"
}
Цифровые товары

Статус и код Apple Gift

GET/order/status/digital_goods/apple_gifts/

Передайте shop_id и ровно один идентификатор. После подготовки подарка поле coupon_code будет содержать код.

Поле bodyТипОписание
shop_id *integer

Идентификатор магазина

order_idstring

ID заказа Apple Gift

merchant_order_idstring

ID заказа в системе мерчанта

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();
Ответ · 200
{
  "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

Не удалось подготовить подарок.

Уведомления

Webhook о платеже

UPay отправляет событие payment.succeeded на webhook_url заказа. Отвечайте любым HTTP-кодом 2xx только после успешного сохранения события.

ЗаголовокX-UPay-Signature: <JWT>
ИдемпотентностьX-UPay-Event-Id: evt_123
Webhook body
{
  "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"
}
Безопасность webhook

Проверка JWT-подписи

Подпись создаётся алгоритмом HS256 с Secret Token того магазина, которому принадлежит заказ. Проверяйте токен до обработки тела.

  1. 1

    Прочитайте точные байты тела запроса до JSON-декодирования.

  2. 2

    Проверьте JWT из X-UPay-Signature ключом магазина и разрешите только HS256.

  3. 3

    Вычислите SHA-256 тела и сравните с claim payload_sha256.

  4. 4

    Сравните event_id, transaction_id, order_id и shop_id между JWT и body.

  5. 5

    Если 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 во всех попытках.

✓

2xx

Событие считается доставленным.

↻

4xx / 5xx

Событие ставится в очередь повторно.

◷

Timeout

UPay повторит запрос позднее.

Справочник

Статусы

Состояние заказа

ACTIVE

Ссылка доступна для оплаты.

COMPLETED

Одноразовый заказ успешно оплачен.

EXPIRED

Срок действия одноразовой ссылки истёк.

Состояние транзакции

PROCESSING

Платёж обрабатывается.

Платёж подтверждён.

DECLINED

Платёж отклонён.

Справочник

Ошибки API

Новый status API возвращает стабильный машиночитаемый формат ошибки.

Ошибка · 404
{
  "success": false,
  "error": {
    "code": "ORDER_NOT_FOUND",
    "message": "Order not found"
  }
}
400INVALID_ORDER_IDENTIFIER

Не передан идентификатор или переданы оба сразу.

400INVALID_TRANSACTIONS_LIMIT

Лимит находится вне диапазона 1–100.

400INVALID_CURSOR

Курсор не принадлежит этому заказу.

401INVALID_SIGNATURE

JWT отсутствует, истёк или подписан неверным ключом.

404ORDER_NOT_FOUND

Заказ не найден в указанном магазине.

409MERCHANT_ORDER_ID_NOT_UNIQUE

Найдено несколько заказов; используйте order_id.

API

Документация для быстрой и безопасной интеграции.

Наверх ↑