РазработчикамAPI

От запроса
до оплаты.

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

BASE URLhttps://xaocpay.store

Доступ к API выдаётся после подключения проекта. Документация открыта для всех. Ключи создаются в настройках вашего мерчанта и хранятся только на сервере.

01

Аутентификация

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

ЗАГОЛОВОКЗНАЧЕНИЕ
X-API-KeyAPI-ключ мерчанта
X-TimestampUnix-время в секундах, допуск ±5 минут
X-NonceНовый UUID для каждого запроса
X-SignatureHMAC-SHA256 в lowercase hex

Подпишите точные UTF-8 байты строки:

Строка подписи
timestamp + '.' + nonce + '.' + METHOD + '.' + pathname + '.' + rawBody

Метод — заглавными буквами. Pathname включает /api/v1/ и не включает query string. Для GET тело пустое: строка заканчивается точкой. Nonce нельзя повторять в течение 10 минут.

02

Создать платёж

POST/api/v1/paymentsJSON

Ответ содержит checkout_url — вашу ссылку на страницу оплаты. Назначение и идентификатор покупателя будут видны в кабинете и на форме.

ПОЛЕТИПОПИСАНИЕ
amount *stringСумма RUB, от 1 до 1 000 000. Например «1490.00»
order_id *stringУникальный номер заказа, до 100 символов
descriptionstringНазначение платежа, до 500 символов
customer_idstringID или имя покупателя, до 100 символов
methodstringauto — общая форма (по умолчанию), sbp или crypto. Также принимается general.
buyer_shareintegerДоля общей комиссии покупателя: 0–100%
metadataobjectВаши данные заказа, до 8 КБ
create-payment.mjs
import { createHmac, randomUUID } from 'node:crypto';

const base = process.env.XAOC_PAY_URL;
const key = process.env.XAOC_API_KEY;
const secret = process.env.XAOC_SIGNING_SECRET;
const path = '/api/v1/payments';
const body = JSON.stringify({
  amount: '1490.00',
  order_id: 'order_1048',
  description: 'Оплата заказа №1048',
  customer_id: 'customer_1048',
  method: 'auto',
  buyer_share: 100,
  metadata: { product: 'subscription' }
});

const ts = String(Math.floor(Date.now() / 1000));
const nonce = randomUUID();
const message = ts + '.' + nonce + '.POST.' + path + '.' + body;
const signature = createHmac('sha256', secret)
  .update(message).digest('hex');

const response = await fetch(base + path, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-API-Key': key,
    'X-Timestamp': ts,
    'X-Nonce': nonce,
    'X-Signature': signature
  },
  body
});
const payment = await response.json();
if (!response.ok) throw new Error(payment.error);
console.log(payment.checkout_url);

При повторе запроса сохраняйте тот же order_id и параметры, но создавайте новый nonce. Одинаковый заказ вернёт существующий платёж; изменённые параметры — ошибку 409.

Общая платёжная форма

Не указывайте method или передайте auto. Покупатель увидит доступные способы и отдельную итоговую сумму для каждого. Реквизиты создаются после выбора. В ответе method_options содержит варианты и суммы. Для прямого перехода к СБП или крипте укажите соответствующий метод.

Распределение комиссии

buyer_share: 0 — комиссию оплачивает магазин. 100 — покупатель. Значение 50 делит её поровну. Распределяется общая комиссия: ставка канала плюс наша ставка. Администратор задаёт нашу ставку для пользователя, а комиссия канала определяется по его ответам или настраивается в админке. Расчёт выполняется до копейки. Без известной ставки канала счёт не создаётся. Конвертация и зачисление учитываются по факту.

Пример ответа

До выбора способа charged_amount и platform_fee равны null. Суммы вариантов зависят от тарифов вашего мерчанта.

201 Created
{
  "payment_id": "pay_…",
  "order_id": "order_1048",
  "status": "created",
  "amount": "1490.00",
  "charged_amount": null,
  "platform_fee": null,
  "currency": "RUB",
  "method": "auto",
  "method_options": [
    {
      "method": "sbp",
      "charged_amount": "1752.95",
      "merchant_net": "1490.00"
    },
    {
      "method": "crypto",
      "charged_amount": "1619.57",
      "merchant_net": "1490.00"
    }
  ],
  "checkout_url": "https://xaocpay.store/pay/pay_…"
}

Список платежей

GET/api/v1/payments

Вся история мерчанта с пагинацией: page, limit (до 100), status, method, q, from, to, min, max, sort. Ответ: items, total, page, pages, hasMore. Даты фильтров — по Москве.

Список показывает сохранённые статусы. Для сверки с каналом запросите отдельный платёж. В подпись GET входит путь без query string и пустое тело, как и раньше.

03

Статус платежа

GET/api/v1/payments/:payment_id

Подписанный запрос без тела. Данные доступны только владельцу соответствующего мерчанта.

createdОжидает оплаты
processingПлатёж обрабатывается
paidОплата подтверждена
expiredИстекло время оплаты
canceledОтменён или отклонён
failedОшибка создания или обработки
refundedСредства возвращены
chargebackПлатёж отозван

Не подтверждайте заказ по возврату покупателя на сайт. Дождитесь paid в API или проверенного вебхука. Поздняя оплата может изменить expired на paid; после оплаты возможны возврат и чарджбэк.

04

Баланс

GET/api/v1/balance
200 OK
{
  "available": 125.50,
  "reserved": 20.00
}

Все суммы — USDT. Баланс формируется из фактических зачислений после конвертации. Одобрение платежа и появление доступного остатка могут происходить в разное время.

05

Вебхуки

Укажите публичный HTTPS-адрес в настройках интеграции. События подписываются тем же секретом мерчанта.

Подпись вебхука
HMAC-SHA256(signing_secret, timestamp + '.' + rawBody)

Заголовки: X-Timestamp, X-Signature, X-Event-ID. Сверьте подпись, время, сумму и валюту. Дедуплицируйте ID события, сохраните его и ответьте HTTP 2xx.

Кнопка проверки в кабинете отправляет integration.test с test: true, без платёжного ID и сумм. Ответьте 2xx, но не выдавайте товар и не меняйте баланс по этому событию.

verify-webhook.mjs
import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody — точные байты запроса, до JSON.parse().
const expected = createHmac('sha256', process.env.XAOC_SIGNING_SECRET)
  .update(timestamp + '.' + rawBody).digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(signature, 'hex');
if (a.length !== b.length || !timingSafeEqual(a, b)) {
  throw new Error('Invalid webhook signature');
}
if (Math.abs(Date.now()/1000 - Number(timestamp)) > 300) {
  throw new Error('Expired webhook timestamp');
}
const event = JSON.parse(rawBody);
// integration.test: подтвердите HTTP 2xx без выдачи товара.
// Для оплаты обрабатывайте только payment.paid с test === false.
// Сверьте payment_id, order_id, сумму и валюту.
// Дедуплицируйте X-Event-ID, затем ответьте HTTP 2xx.

Доступно до 8 попыток доставки с увеличивающимся интервалом. В текущей версии обработка очереди запускается при обращениях к платформе. Повтор можно выполнить вручную из кабинета. Для независимой сверки используйте GET статуса.

06

Ошибки API

400Некорректные параметры
401Неверные ключ, подпись или timestamp
403Нет доступа или аккаунт приостановлен
404Платёж не найден
409Конфликт заказа или повтор nonce
429Слишком много запросов
502 / 503Канал оплаты временно недоступен

При сетевом тайм-ауте повторите тот же заказ с новым nonce. Не создавайте другой order_id, пока результат предыдущей попытки не установлен.

Готовы подключаться?Откройте кабинет мерчанта