Webhooks и редиректы
CryptumPay уведомляет ваш бэкенд о событиях оплаты двумя способами:
- Webhooks — HTTP
POST-запросы, отправляемые на ваш эндпоинт при изменении статуса заказа - Редиректы — платёжный шлюз перенаправляет браузер клиента на ваш
successPathилиfailPathпосле завершения оплаты
Webhooks
Настройка
Настройте URL вебхука в Консоли CryptumPay на странице проекта. URL должен быть доступен из интернета и использовать HTTPS с валидным сертификатом.
Только HTTPS
Сервис доставки вебхуков проверяет SSL-сертификат вашего эндпоинта. HTTP или самоподписанные сертификаты приведут к ошибке доставки.
Формат входящего запроса
Вебхуки доставляются как POST-запросы со следующими заголовками:
| Заголовок | Описание |
|---|---|
Content-Type | application/json |
X-Signature | HMAC-подпись для верификации payload (см. Валидация вебхуков) |
X-IDMP-Key | UUID — ключ идемпотентности; одно событие может быть повторно доставлено с тем же ключом |
User-Agent | CryptumPayNotifier |
Ваш эндпоинт должен ответить статусом 2xx для подтверждения получения. Любой другой ответ (включая редиректы) считается ошибкой и запускает повторную доставку.
Повторные попытки
При ошибке доставки система повторяет попытки с экспоненциальной задержкой — до 12 попыток на протяжении примерно 1 часа. После 12 неудачных попыток вебхук отбрасывается.
Используйте X-IDMP-Key для дедупликации событий при повторных доставках.
Типы вебхуков
merchantOrder
Отправляется при создании заказа мерчанта.
{
"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
{
"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
{
"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 вашего вебхука, может отправить поддельные уведомления об оплате и спровоцировать исполнение заказов, которые на самом деле не были оплачены.
Алгоритм (независимо от языка)
Получите сырое тело запроса — до любого JSON-парсинга. Тело должно быть точно такими же байтами, как получено.
Хешируйте тело
bodyHash = hex(SHA-256(rawBody))Сформируйте строку для подписи
prehash = bodyHash + "|" + apiKeyВычислите ожидаемую подпись
expectedSignature = hex(HMAC-SHA256(prehash, secret))Сравните подписи Сравните
expectedSignatureсо значениемX-Signature. Используйте сравнение с постоянным временем для защиты от timing-атак.
Пример: Node.js SDK
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)
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-параметры
| Параметр | Описание |
|---|---|
cpayorder | Order ID мерчанта |
cpayproject | ID вашего проекта |
Используйте их для поиска заказа в вашей системе и отображения соответствующей страницы подтверждения.
Верификация на бэкенде
Никогда не полагайтесь только на редирект для подтверждения платежа. Всегда проверяйте финальный статус заказа на стороне сервера — через вебхуки или вызов GET /v1/orders/:orderId.
Пример: обработка успешного редиректа
// 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.
Смотрите также
- Аутентификация — API-ключи и подпись запросов
- API Эндпоинты — полный справочник эндпоинтов