ExpressPay API

Оплата QR-кодов СБП по API

Вы отправляете ссылку QR-кода и сумму — мы оплачиваем её со своего шлюза и списываем эквивалент с вашего баланса в USDT. Комиссия за платёж — 0%.

Как это работает

  1. Вы заранее пополняете баланс в USDT.
  2. Ваш сервис отправляет POST /v1/payments со ссылкой QR.
  3. Мы проверяем реквизиты, платим и списываем USDT по текущему курсу.
  4. Результат приходит в ответе, а также вебхуком на ваш адрес.
Курс и стоимость Списание считается по биржевому курсу USDT/RUB, который обновляется каждую минуту. Актуальный курс — в GET /v1/rate, предварительный расчёт — в POST /v1/quote. Отдельной комиссии за платёж нет: стоимость услуги уже заложена в курс.

1. Ключ

API-ключ выдаёт бот @ExpressPay_robot — раздел «API для бизнеса». Ключ показывается один раз, хранится только его хеш. Ключ даёт право тратить ваш баланс, поэтому храните его как пароль и не кладите в клиентский код.

Authorization: Bearer ep_live_1a2b3c4d_xxxxxxxxxxxxxxxxxxxxxxxx

Проверить ключ:

curl https://api.exppay.net/v1/ping \
  -H "Authorization: Bearer $EXPRESSPAY_KEY"
{"ok": true, "partner_id": "prt_a1b2c3d4e5f6", "mode": "live", "api_version": "1.0"}

2. Первый платёж

Заголовок Idempotency-Key обязателен: по нему мы отличаем повтор запроса от второго платежа. Сгенерируйте его сами (UUID подойдёт) и используйте один и тот же ключ при всех повторах одного и того же платежа.

curl https://api.exppay.net/v1/payments \
  -H "Authorization: Bearer $EXPRESSPAY_KEY" \
  -H "Idempotency-Key: 3f1c9a2e-7b44-4c1e-9c3a-2a1f5f0d9e77" \
  -H "Content-Type: application/json" \
  -d '{
    "qr_url": "https://qr.nspk.ru/AS1A0000000000000000000000000001",
    "amount_rub": "1450.00",
    "external_id": "order-4412"
  }'
{
  "id": "pay_9f3c1a2b4d5e",
  "object": "payment",
  "mode": "live",
  "status": "succeeded",
  "status_detail": null,
  "external_id": "order-4412",
  "recipient": "ООО «Ромашка»",
  "amount_rub": "1450.00",
  "amount_usdt": "17.330692",
  "rate": "83.6666",
  "currency": "RUB",
  "settlement_currency": "USDT",
  "created_at": "2026-09-15T10:22:31.481920+00:00",
  "settled_at": "2026-09-15T10:22:48.113044+00:00",
  "error": null,
  "metadata": {}
}
Поле amount_rub в запросе необязательно, но мы рекомендуем его передавать Мы сверяем его с суммой, зашитой в QR-код, до копейки и отказываем с ошибкой amount_mismatch, если они разошлись. Это защищает вас от оплаты подменённого QR-кода.

3. Проверка результата

Платёж не всегда завершается мгновенно. Если в ответе status равен processing, дождитесь вебхука или опросите статус:

curl https://api.exppay.net/v1/payments/pay_9f3c1a2b4d5e \
  -H "Authorization: Bearer $EXPRESSPAY_KEY"

Источник истины — всегда GET /v1/payments/{id}. Подробнее о статусах — в разделе Платежи.

Примеры на языках

PHP

$ch = curl_init('https://api.exppay.net/v1/payments');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('EXPRESSPAY_KEY'),
        'Idempotency-Key: ' . $orderUuid,
        'Content-Type: application/json',
    ],
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        'qr_url'      => $qrUrl,
        'amount_rub'  => '1450.00',
        'external_id' => $orderId,
    ]),
]);
$payment = json_decode(curl_exec($ch), true);

Python

import os, uuid, httpx

r = httpx.post(
    "https://api.exppay.net/v1/payments",
    headers={
        "Authorization": f"Bearer {os.environ['EXPRESSPAY_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"qr_url": qr_url, "amount_rub": "1450.00", "external_id": order_id},
    timeout=120,
)
payment = r.json()
if r.status_code != 200:
    raise RuntimeError(payment["error"]["code"])

Node.js

const res = await fetch("https://api.exppay.net/v1/payments", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.EXPRESSPAY_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ qr_url: qrUrl, amount_rub: "1450.00", external_id: orderId }),
});
const payment = await res.json();
Таймауты на вашей стороне Запрос может выполняться до 90 секунд: мы держим соединение, пока платёжный шлюз не ответит. Ставьте таймаут HTTP-клиента не меньше 120 секунд. Если соединение всё же оборвалось — не создавайте новый платёж, а повторите запрос с тем же Idempotency-Key или запросите статус.

Песочница

Тестовый ключ (ep_test_…) работает с теми же адресами, но платежи не уходят в банк. Исход задаётся копейками суммы — это единственный способ проверить редкие ветки, не рискуя деньгами:

Сумма заканчивается наЧто произойдёт
,01отказ, средства возвращаются на тестовый баланс
,02неизвестный исход: processing, через 30 секунд — успех
,03расхождение данных QR (qr_changed)
прочееуспех