Skip to content

Webhooks и редиректы

CryptumPay уведомляет ваш бэкенд о событиях оплаты двумя способами:

  • Webhooks — HTTP POST-запросы, отправляемые на ваш эндпоинт при изменении статуса заказа
  • Редиректы — платёжный шлюз перенаправляет браузер клиента на ваш successPath или failPath после завершения оплаты

Webhooks

Настройка

Настройте URL вебхука в Консоли CryptumPay на странице проекта. URL должен быть доступен из интернета и использовать HTTPS с валидным сертификатом.

Только HTTPS

Сервис доставки вебхуков проверяет SSL-сертификат вашего эндпоинта. HTTP или самоподписанные сертификаты приведут к ошибке доставки.

Формат входящего запроса

Вебхуки доставляются как POST-запросы со следующими заголовками:

ЗаголовокОписание
Content-Typeapplication/json
X-SignatureHMAC-подпись для верификации payload (см. Валидация вебхуков)
X-IDMP-KeyUUID — ключ идемпотентности; одно событие может быть повторно доставлено с тем же ключом
User-AgentCryptumPayNotifier

Ваш эндпоинт должен ответить статусом 2xx для подтверждения получения. Любой другой ответ (включая редиректы) считается ошибкой и запускает повторную доставку.

Повторные попытки

При ошибке доставки система повторяет попытки с экспоненциальной задержкой — до 12 попыток на протяжении примерно 1 часа. После 12 неудачных попыток вебхук отбрасывается.

Используйте X-IDMP-Key для дедупликации событий при повторных доставках.


Типы вебхуков

merchantOrder

Отправляется при создании заказа мерчанта.

json
{
  "type": "merchantOrder",
  "id": "1c885afe-369f-4b38-92fa-983660c2452d",
  "title": "Donation",
  "description": "Support our project",
  "url": null,
  "fiatCurrency": "usd",
  "fiatAmount": "0.8",
  "projectDataUserId": null,
  "projectDataUserEmail": null,
  "projectDataMeta": null,
  "projectDataOrderId": null,
  "createdAt": "2026-04-18T01:50:40.235Z",
  "updatedAt": "2026-04-18T01:50:40.346Z",
  "expiresAt": "2026-04-25T01:50:40.234Z"
}

customerOrder

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

Жизненный цикл статусов

created → pending → crediting → finished
СтатусЗначение
createdКлиент выбрал криптовалюту, адрес депозита назначен. Ожидание платежа.
pendingВходящая транзакция обнаружена в блокчейне. Ожидание подтверждений.
creditingПолучено достаточно подтверждений. Платёж зачисляется на баланс мерчанта.
finishedОплата полностью завершена. Поле income содержит сумму в фиате после всех комиссий.

Пример: created

json
{
  "type": "customerOrder",
  "id": "6fcfa357-7bd5-4f3e-9abc-5a85ab7c1221",
  "merchantOrderId": "1c885afe-369f-4b38-92fa-983660c2452d",
  "cryptoCurrency": "bnb",
  "cryptoAmount": "0.001355",
  "status": "created",
  "createdAt": "2026-04-18T01:50:40.341Z",
  "updatedAt": "2026-04-18T01:50:40.341Z",
  "expiresAt": "2026-04-18T02:20:40.340Z"
}

Пример: finished

json
{
  "type": "customerOrder",
  "id": "6fcfa357-7bd5-4f3e-9abc-5a85ab7c1221",
  "merchantOrderId": "1c885afe-369f-4b38-92fa-983660c2452d",
  "cryptoCurrency": "bnb",
  "cryptoAmount": "0.001355",
  "status": "finished",
  "income": "0.799169",
  "createdAt": "2026-04-18T01:50:40.341Z",
  "updatedAt": "2026-04-18T01:52:19.207Z",
  "expiresAt": "2026-04-18T02:20:40.340Z"
}

TIP

Поле income (сумма в фиате после комиссий) присутствует только в вебхуке со статусом finished.


Валидация вебхуков

Всегда валидируйте заголовок X-Signature перед обработкой вебхука. Это гарантирует, что payload действительно отправлен CryptumPay и не был подменён.

Почему это важно

Без валидации любой, кто знает URL вашего вебхука, может отправить поддельные уведомления об оплате и спровоцировать исполнение заказов, которые на самом деле не были оплачены.

Алгоритм (независимо от языка)

  1. Получите сырое тело запроса — до любого JSON-парсинга. Тело должно быть точно такими же байтами, как получено.

  2. Хешируйте телоbodyHash = hex(SHA-256(rawBody))

  3. Сформируйте строку для подписиprehash = bodyHash + "|" + apiKey

  4. Вычислите ожидаемую подписьexpectedSignature = hex(HMAC-SHA256(prehash, secret))

  5. Сравните подписи Сравните expectedSignature со значением X-Signature. Используйте сравнение с постоянным временем для защиты от timing-атак.

Пример: Node.js SDK

typescript
import { CryptumPaySigner } from '@cryptumpay/node-sdk';

const signer = new CryptumPaySigner(
  process.env.CRYPTUMPAY_API_KEY,
  process.env.CRYPTUMPAY_API_SECRET
);

// В обработчике вебхука (пример Express):
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-signature'] as string;

  // Передавайте сырой Buffer или строку — НЕ передавайте req.body после JSON.parse
  const isValid = signer.verifyCallback(signature, req.body.toString());

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const event = JSON.parse(req.body.toString());

  // Обрабатываем событие
  console.log('Получено событие:', event.type, event.id);

  res.status(200).send('OK');
});

Читайте сырое тело

Используйте express.raw() (или аналог) вместо express.json() для маршрута вебхука. JSON.stringify(JSON.parse(body)) может вернуть строку, отличную от оригинала, что сломает верификацию подписи.

Пример: ручная валидация (Node.js)

typescript
import crypto from 'crypto';

function verifyWebhook(
  rawBody: string,
  signature: string,
  apiKey: string,
  secret: string
): boolean {
  const bodyHash = crypto.createHash('sha256').update(rawBody).digest('hex');
  const prehash = bodyHash + '|' + apiKey;
  const expected = crypto.createHmac('sha256', secret).update(prehash).digest('hex');

  // Сравнение с постоянным временем
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

Редиректы

Помимо вебхуков, CryptumPay перенаправляет браузер клиента на ваш сайт после завершения оплаты. Редиректы настраиваются для каждого домена в консоли.

Настройка

В Консоли CryptumPay перейдите в ваш проект → Domain. Для каждого домена можно настроить:

  • successPath — путь для редиректа после успешной оплаты
  • failPath — путь для редиректа при истечении или отмене оплаты

Пути должны начинаться с /, например /payment/success или /checkout/cancelled.

Формат URL редиректа

После оплаты клиент перенаправляется на:

https://{host}{successPath}?cpayorder={orderId}&cpayproject={projectId}

При ошибке или отмене:

https://{host}{failPath}?cpayorder={orderId}&cpayproject={projectId}

Query-параметры

ПараметрОписание
cpayorderOrder ID мерчанта
cpayprojectID вашего проекта

Используйте их для поиска заказа в вашей системе и отображения соответствующей страницы подтверждения.

Верификация на бэкенде

Никогда не полагайтесь только на редирект для подтверждения платежа. Всегда проверяйте финальный статус заказа на стороне сервера — через вебхуки или вызов GET /v1/orders/:orderId.

Пример: обработка успешного редиректа

typescript
// Express-обработчик для /payment/success
app.get('/payment/success', async (req, res) => {
  const orderId = req.query.cpayorder as string;

  // Проверяем на сервере
  const response = await client.getOrder(orderId);

  if (response.errorObject) {
    return res.redirect('/payment/failed');
  }

  const status = response.data.financeSummary.status;

  // status 3 = Оплачен, status 4 = Зачисление — см. статусы financeSummary в API Эндпоинтах
  const isPaid = status === 3 || status === 4;
  if (!isPaid) {
    return res.redirect('/payment/failed');
  }

  // Помечаем заказ как оплаченный в базе данных
  await db.orders.markPaid(orderId, response.data.financeSummary.income);

  res.render('payment-success', { orderId });
});

Подтверждение успешного платежа

Для подтверждения получения средств проверьте, что financeSummary.status равен 3 или 4:

  • Статус 3 (Оплачен) — средства уже зачислены на баланс проекта.
  • Статус 4 (Зачисление) — зачисление ещё в процессе, но система точно получила платёж и завершит перевод в ближайшее время.

Полный список статусов — в разделе Статусы financeSummary.

Смотрите также

Распространяется под лицензией MIT.