Nudge Pay API

Приём оплат в крипте для сайтов, ботов и каналов в Telegram. Покупатель платит со своего баланса в Nudge Wallet, деньги приходят на баланс магазина сразу.

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

  1. Твой сервер создаёт счёт и получает ссылку на оплату.
  2. Ты отдаёшь ссылку покупателю: кнопкой в боте, на сайте, в сообщении.
  3. Покупатель открывает ссылку в Telegram и платит в два нажатия.
  4. Nudge присылает на твой сервер вебхук invoice.paid. Деньги уже на балансе магазина.

Комиссия — 1% от суммы, удерживается с магазина. Покупатель платит ровно сумму счёта. Принимаем USDT, TON и TRX.

Начало

Открой Nudge Wallet → Профиль → Nudge Pay, подключи приём оплат и скопируй ключ API. Ключ показывается один раз; если потерял — создай новый, старый сразу перестанет работать.

Базовый адрес API: https://…

Ключ передаётся в заголовке:

Authorization: Bearer nk_live_…

Храни ключ только на сервере. С ним можно создавать и отменять счета магазина, но нельзя выводить деньги.

Создать счёт

POST/pay/v1/invoices
ПолеТипОписание
assetstringМонета: USDT, TON или TRX.
amountnumberСумма, которую заплатит покупатель. Не меньше $0.10.
descriptionstringНеобязательно. Что покупают, до 200 символов. Видно покупателю.
external_idstringНеобязательно. Твой номер заказа, до 128 символов. Повторный запрос с тем же external_id вернёт уже созданный счёт, а не новый.
payloadstring / objectНеобязательно. Любые данные до 1024 символов, вернутся в вебхуке как есть.
expires_innumberНеобязательно. Сколько секунд счёт действует: от 60 до 2 592 000 (30 дней). По умолчанию сутки.
curl -X POST https://…/pay/v1/invoices \
  -H "Authorization: Bearer $NUDGE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"asset":"USDT","amount":5,"description":"Подписка на месяц","external_id":"order-1042"}'

Ответ 201:

{
  "id": "inv_Xa9…",
  "status": "pending",
  "asset": "USDT",
  "amount": 5,
  "fee": 0,
  "amount_received": null,
  "description": "Подписка на месяц",
  "external_id": "order-1042",
  "payload": null,
  "pay_url": "https://t.me/NudgeWallet_bot?startapp=p_inv_Xa9…",
  "web_url": null,
  "created_at": "2026-09-23T18:00:00.000Z",
  "expires_at": "2026-09-24T18:00:00.000Z",
  "paid_at": null
}

pay_url — ссылка, которую нужно отдать покупателю. После оплаты в fee будет удержанная комиссия, в amount_received — сколько пришло магазину.

Другие запросы

GET/pay/v1/invoices/{id} — счёт по id.
GET/pay/v1/invoices?limit=20 — последние счета, до 100.
POST/pay/v1/invoices/{id}/cancel — отменить неоплаченный счёт.
GET/pay/v1/me — проверить ключ: название магазина и комиссия.

Статусы счёта

СтатусЧто значит
pendingЖдёт оплаты.
paidОплачен, деньги на балансе магазина. Окончательный статус.
expiredИстёк срок, оплатить уже нельзя.
cancelledОтменён магазином.

Вебхуки

Укажи адрес в кабинете (Nudge Pay → Адрес вебхука). Адрес должен быть публичным и начинаться с https://. Кнопка «Проверить» отправит событие test.

Nudge отправляет POST с JSON:

{ "event": "invoice.paid", "invoice": { …тот же объект, что в API… } }

События: invoice.paid, invoice.expired, invoice.cancelled, test.

Ответь любым кодом 2xx. Если ответа нет или код другой, повторим до 8 раз с растущей паузой: 30 секунд, минута, 2 минуты и так далее. Одно и то же событие может прийти дважды, поэтому проверяй invoice.id и не выдавай товар повторно.

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

В заголовке X-Nudge-Signature: t=1758650000,v1=… лежит время отправки и HMAC-SHA256 от строки {t}.{тело запроса} с секретом вебхука из кабинета. Проверяй подпись по сырому телу запроса, до разбора JSON, и отклоняй запросы старше 5 минут.

// Node.js (Express)
import crypto from 'node:crypto';
import express from 'express';

const app = express();
app.post('/nudge-webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-Nudge-Signature') ?? '';
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto
    .createHmac('sha256', process.env.NUDGE_WEBHOOK_SECRET)
    .update(`${parts.t}.${req.body}`)
    .digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  const valid = parts.v1 && expected.length === parts.v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  if (!fresh || !valid) return res.sendStatus(400);

  const { event, invoice } = JSON.parse(req.body);
  if (event === 'invoice.paid') {
    // выдай товар по invoice.external_id, если ещё не выдан
  }
  res.sendStatus(200);
});
# Python
import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    signed = f"{parts['t']}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    fresh = abs(time.time() - int(parts["t"])) < 300
    return fresh and hmac.compare_digest(expected, parts.get("v1", ""))

Ошибки

При ошибке ответ — JSON { "error": "код", "message": "текст" }.

HTTPerrorКогда
401unauthorizedНет ключа или он неверный.
400bad_asset, bad_amount, too_small, bad_requestНеверные поля запроса.
404not_foundСчёта нет или он другого магазина.
409already_paidОтмена оплаченного или закрытого счёта.
429rate_limitedБольше 60 запросов в минуту.

Ограничения