Перейти к содержимому
Документация
На этой странице

Руководство по интеграции

Документация для разработчиков

Создайте счёт, отправьте клиента на страницу оплаты и получите подписанный вебхук после оплаты. Начните с четырёх шагов ниже.

  1. Скопируйте API-ключ проекта.
  2. Подписывайте изменяющие запросы.
  3. Создайте первый счёт.
  4. Проверьте вебхук об оплате.

Что поддерживается

Шлюз принимает USDT и USDC. Эта таблица совпадает с GET /v1/services по состоянию на 4 ноября 2026 г. Сверьтесь с актуальным каталогом, прежде чем делать выбор способа оплаты.

АктивСетьПодтверждения
USDTTron20
USDTEthereum12
USDTSolana32
USDTPolygon8
USDTArbitrum12
USDTTON5
USDCEthereum12
USDCSolana32
USDCPolygon8
USDCBase12
USDCArbitrum12

Платежи кастодиальные: средства поступают на адреса под управлением платформы. Ваш баланс мерчанта — это сумма, которую мы вам должны. Отдельный кошелёк в Telegram — некастодиальный.

Ограничения

  • Выплаты и возвраты пока не работают: GET /v1/services возвращает boundaries.livePayouts и boundaries.liveRefunds со значением false.
  • Входящие платежи не проверяются KYT-провайдером.
  • Счета, в которых комиссии превысили бы 20% суммы, отклоняются с ошибкой AMOUNT_BELOW_MINIMUM.
  • Договорного SLA нет.

Стартовая комиссия — 0,40% плюс комиссия сети. Запросите расчёт комиссии, чтобы узнать итог и минимальную сумму; подробнее — в разделе тарифы.

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

Вызовы между серверами используют x-api-key. Ключ проекта действует только в одном проекте мерчанта. Храните ключи на своём сервере.

Тестовые ключи начинаются с ncp_test_…, боевые — с ncp_live_…. Ключи, выпущенные раньше, начинаются с ncp_sandbox_ и продолжают работать.

curl -sS "https://api.veilnebu.la/v1/balances" \
  -H "x-api-key: $PROJECT_API_KEY"

Подпись изменяющих запросов

GET-запросам нужен только x-api-key. Каждому запросу POST, PUT, PATCH и DELETE с ключом проекта нужны ещё два заголовка, иначе API ответит 401 Invalid API signature:

  • x-ncp-timestamp: ISO-8601, не дальше 5 минут от текущего времени.
  • x-ncp-signature: hex(HMAC-SHA256(your API key, "<timestamp>.<METHOD>.<path incl. query>.<raw body>")).

Пишите методы заглавными буквами. Подписывайте ровно те байты, которые отправляете, включая строку запроса в пути. Для запроса без тела подписывается пустая строка. Хелперам ниже нужен ключ проекта; shell-хелперу нужны Bash, curl и OpenSSL, а хелпер для Node.js использует встроенный fetch.

export API_BASE_URL="https://api.veilnebu.la"
export PROJECT_API_KEY="ncp_test_…"

ncp() {
  local method="$1" path="$2" body="" ts sig
  shift 2
  if [ "$#" -gt 0 ]; then body="$1"; shift; fi
  ts=$(date -u +%Y-%m-%dT%H:%M:%S.000Z) || return
  sig=$(printf '%s.%s.%s.%s' "$ts" "$method" "$path" "$body" |
    openssl dgst -sha256 -hmac "$PROJECT_API_KEY" -hex) || return
  sig=${sig##* }
  if [ -n "$body" ]; then
    curl -sS --request "$method" "$API_BASE_URL$path" \
      -H "x-api-key: $PROJECT_API_KEY" \
      -H "x-ncp-timestamp: $ts" -H "x-ncp-signature: $sig" \
      -H "content-type: application/json" --data-binary "$body" "$@"
  else
    curl -sS --request "$method" "$API_BASE_URL$path" \
      -H "x-api-key: $PROJECT_API_KEY" \
      -H "x-ncp-timestamp: $ts" -H "x-ncp-signature: $sig" "$@"
  fi
}

Создайте первый счёт

Создайте проект в личном кабинете, скопируйте его API-ключ и создайте счёт. В ответе есть checkoutUrl: отправьте туда клиента.

ncp POST /v1/payments '{
  "amountMinor":"10000",
  "currencyCode":"USD",
  "settlementAssetCode":"USDT",
  "externalId":"order-1001"
}' -H "idempotency-key: order-1001"

Фрагмент ответа. ID генерируется для каждого счёта.

{
  "id": "cm3k9x2f40001qz8e7h5v1abc",
  "status": "new",
  "amountMinor": "10000",
  "currencyCode": "USD",
  "settlementAssetCode": "USDT",
  "checkoutUrl": "https://app.veilnebu.la/checkout/cm3k9x2f40001qz8e7h5v1abc"
}

amountMinor — целое число в виде строки в минимальных единицах валюты: 10000 = 100.00 USD. Десятичное поле amount принимает только браузерный эндпоинт /v1/public/payments.

Оплата из браузера и разрешённые источники

Создайте публикуемый ключ в личном кабинете и перечислите точные источники (origins), которые использует ваш магазин. Браузерные запросы передают его как x-publishable-key в POST /v1/public/payments.

Origin, successUrl и cancelUrl должны входить в список разрешённых источников ключа. Источник — это схема, хост и необязательный порт, без пути и подстановочных знаков. Запросы без источника или с источником не из списка отклоняются.

Десятичная сумма допускает до двух знаков после запятой для фиатных валют и до собственного числа знаков актива для криптовалют (USDT и USDC: 6 во всех сетях, которые мы сейчас поддерживаем).

const response = await fetch('https://api.veilnebu.la/v1/public/payments', {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    'x-publishable-key': 'pk_test_…',
  },
  body: JSON.stringify({
    amount: '100.00',
    currencyCode: 'USD',
    settlementAssetCode: 'USDT',
    successUrl: 'https://shop.example/thanks',
    cancelUrl: 'https://shop.example/cart',
  }),
});
if (!response.ok) throw new Error(await response.text());
const invoice = await response.json();
window.location.assign(invoice.checkoutUrl);

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

СтатусСледующее действие
newОтправьте ссылку на оплату
pendingОжидаем оплату
confirmingОжидаем подтверждения
paidГотово к зачислению
partially_paidПопросите клиента доплатить
overpaidПроверьте зачисленную переплату
expiredСоздайте новый счёт
cancelledДействий не требуется
failedОткройте подробности
refundedДействий не требуется

Ключи идемпотентности

При создании счёта передавайте стабильный Idempotency-Key, полученный из ID вашего заказа. Повтор с тем же ключом и телом вернёт исходный счёт. Повторное использование ключа с другим телом вернёт 409. При повторе после тайм-аута не меняйте ни ключ, ни тело.

Сам запрос смотрите в примере первого счёта.

Ошибки

Ошибки возвращаются в JSON. message — строка либо массив строк для ошибок валидации.

{
  "statusCode": 401,
  "error": "Unauthorized",
  "message": "Invalid API key"
}
HTTP-статусЗначение и действие
400Неверное поле; его название указано в "message". Исправьте его перед повтором.
401Invalid API key или Invalid API signature. Проверьте ключ, подписанные байты и метку времени.
404Payment invoice not found. Проверьте ID и проект.
409Тот же Idempotency-Key с другим телом или Insufficient merchant payable balance.
429Превышен лимит запросов. Подождите перед повтором.
5xxПовторите запрос с тем же Idempotency-Key и телом.

Вебхуки (HMAC-SHA256)

Каждая доставка содержит x-nebula-signature: t=<unix>,v1=<hex>. Проверьте HMAC секретом эндпоинта, который вы получили при его создании. Отклоняйте метки времени, отстоящие от текущего момента больше чем на 300 секунд.

Проверка в Node.js

Верификатор для Node.js использует только встроенный модуль crypto. Передайте ему заголовок подписи, исходные байты тела и секрет эндпоинта.

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(header, rawBody, secret) {
  const parts = {};
  for (const piece of (header ?? '').split(',')) {
    const separator = piece.indexOf('=');
    if (separator <= 0) continue;
    parts[piece.slice(0, separator).trim()] = piece.slice(separator + 1).trim();
  }
  if (!/^[0-9]{1,16}$/.test(parts.t ?? '')) return false;
  if (!/^[0-9a-f]{64}$/.test(parts.v1 ?? '')) return false;
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(parts.t + '.')
    .update(rawBody)
    .digest('hex');
  if (expected.length !== parts.v1.length) return false;
  return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

Проверяйте подпись по исходному телу запроса до разбора JSON. Доставка гарантируется как минимум один раз: убирайте дубли по x-nebula-event-id, который совпадает с eventId в теле. Автоматические повторы сохраняют ID доставки; ручная повторная отправка создаёт новый ID доставки с тем же ID события. Сохраните событие, прежде чем ответить 2xx.

Локальное тестирование вебхуков

URL вебхука должен указывать на публичный хост в интернете. Локальные и частные адреса отклоняются. Откройте доступ к своему приёмнику через публичный HTTPS-туннель, а затем зарегистрируйте его URL с помощью хелпера подписи.

ncp POST /v1/webhooks/endpoints '{
  "url":"https://receiver.example/webhook",
  "events":["payment.paid","payment.reorged"]
}'

Замените URL на адрес своего приёмника. Сохраните секрет эндпоинта, который вернётся при создании.

Типы событий

Выплаты и возвраты пока не работают (GET /v1/services возвращает boundaries.livePayouts со значением false). До их запуска выплаты и пакеты выплат только фиксируют перевод в вашем балансе; payout.completed и refund.completed не означают, что средства ушли.

Каталог ниже совпадает с GET /v1/services. Наличие записи в каталоге не означает, что соответствующее движение денег уже работает.

СобытиеЗначение
webhook.testТестовая доставка, запрошенная для эндпоинта вебхука.
payment.pendingКлиент выбрал способ оплаты; ожидаем первый депозит.
payment.confirmingДепозит обнаружен; ожидаем нужное число подтверждений.
payment.paidСчёт полностью оплачен и проведён по книге учёта.
payment.partially_paidПолучено меньше запрошенного; ожидаем остаток.
payment.overpaidПолучено больше запрошенного; проведено по книге учёта вместе с переплатой.
payment.expiredСрок счёта истёк, а средств поступило недостаточно.
payment.cancelledСчёт отменён до оплаты.
payment.failedПлатёж не прошёл из-за ошибки сети, политики комиссий или наблюдения.
payment.risk_flaggedИнформационное событие о риске по уже зачисленному депозиту. Проверка входящих платежей через KYT не работает.
payment.reorgedПодтверждённый депозит пропал из-за реорганизации блокчейна; счёт больше не считается оплаченным, поэтому приостановите или отмените выполнение заказа.
payment.unsupported_assetПоступил неподдерживаемый токен; он не зачислен.
payout.processingВыплата в обработке. Реальные переводы не открыты.
payout.completedВыплата записана в баланс; это не значит, что средства ушли.
payout.failedВыплата не прошла.
payout.cancelledВыплата отменена до завершения.
refund.processingВозврат в обработке. Реальные возвраты не открыты.
refund.completedВозврат записан в баланс; это не значит, что средства ушли.
refund.failedВозврат не прошёл.
internal_transfer.sentПеревод баланса между мерчантами проведён у отправителя.
internal_transfer.receivedПеревод баланса между мерчантами зачислен получателю.
static_wallet.createdДля клиента создан постоянный адрес для депозитов.
static_wallet.blockedСтатический кошелёк заблокирован.
static_wallet.unblockedБлокировка статического кошелька снята.
static_wallet.topup.completedДепозит на статический кошелёк обнаружен и проведён по книге учёта.
dispute.openedПо счёту открыт спор.
dispute.resolvedСпор урегулирован. Само урегулирование не доказывает возврат в блокчейне.
dispute.rejectedСпор отклонён; от мерчанта действий не требуется.
subscription.invoice_createdПлатёжный цикл создал новый счёт.

Политика повторов

Если ваш эндпоинт не отвечает 2xx, мы делаем 8 попыток в сумме: первая попытка, затем повторы через 1 минуту, 5 минут, 30 минут, 2 часа, 6 часов, 12 часов и 24 часа. Каждая задержка отсчитывается от предыдущей неудачной попытки. Сетевые ошибки и тайм-ауты тоже считаются неудачами. Для тестовых доставок — одна попытка.

После последней неудачи доставка помечается как неуспешная; отправьте её повторно через POST /v1/webhooks/deliveries/{deliveryId}/resend. Для сверки в качестве запасного варианта используйте GET /v1/payments/{id}.

Выплаты и возвраты пока не работают (GET /v1/services возвращает boundaries.livePayouts со значением false). До их запуска выплаты и пакеты выплат только фиксируют перевод в вашем балансе; payout.completed и refund.completed не означают, что средства ушли.

Пакеты выплат

Пакет объединяет записи о переводах. Его политика одобрения и нужное число одобрений возвращаются в approvalPolicy и approvalsRequired. Если одобряющих несколько, создатель не может одобрить свой пакет. Единственный допущенный оператор может одобрить пакет, созданный в его же сессии. Пакеты одобряются через WebAuthn в личном кабинете.

export PROJECT_API_KEY="ncp_test_…"
ncp POST /v1/payout-batches '{
  "assetCode":"USDT",
  "networkCode":"TRON",
  "externalId":"batch-1001",
  "items":[{
    "destinationAddress":"T…",
    "amountMinor":"15000000",
    "externalRef":"recipient-1001"
  }]
}' -H "idempotency-key: batch-1001"

Замените адрес назначения действительным адресом в своём тестовом проекте.

Регулярные платежи

Подписка создаёт новый счёт в каждом платёжном цикле и сообщает о нём событием subscription.invoice_created. Она не списывает средства с кошелька клиента автоматически. Отправьте клиенту новый URL страницы оплаты.

interval: DAY, WEEK, MONTH или YEAR.

ncp POST /v1/subscriptions '{
  "customerRef":"customer-42",
  "assetCode":"USDT",
  "networkCode":"TRON",
  "amountMinor":"9000000",
  "interval":"MONTH",
  "intervalCount":1
}'

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

Проверка рисков

Адреса назначения выплат и возвратов проверяются по публичным санкционным спискам до создания перевода; при совпадении запрос отклоняется. Входящие платежи не проверяются.

Политика AML и санкций

Бот Telegram для мерчантов

@veilnebula_bot работает с одним проектом за раз. Привяжите его в личном чате с помощью API-ключа этого проекта. Бот пытается удалить ваше сообщение и хранит ключ в зашифрованном виде. Если удалить не получилось, отзовите ключ и выпустите новый. Никогда не публикуйте ключ в общих чатах.

Уведомления относятся к привязанному проекту и активному ключу. Чтобы одобрить выплаты, откройте личный кабинет.

/link <API_KEY>
/balance
/invoices
/notify on
/unlink
Руководство по интеграции · VeilNebula