Что такое webhooks xpayment, когда они срабатывают, как их настроить и как обрабатывать события платежей и возвратов.
Вебхук — это HTTP-запрос, который xpayment отправляет на ваш сервер, когда с платежом что-то происходит. Например, когда покупатель оплатил счёт или отклонил его.
Представьте разницу между двумя подходами:
GET /v1/payments/{id} и проверяет: «А как там платёж?». Это работает, но создаёт лишнюю нагрузку и задержку.POST /your-endpoint и говорит: «Эй, вот что случилось».Вебхук — это push-уведомление для серверов.
Вебхук — это завершающее звено в обоих платёжных сценариях xpayment:
POST /v1/payments)Ваш сервер → xpayment → Kaspi (push на телефон покупателя)
↓ покупатель оплачивает
Ваш сервер ← xpayment (webhook) ← Kaspi (меняет статус)
Шаг 7 из 8 в потоке прямого платежа — именно здесь срабатывает вебхук. Без него ваш сервер не знает, когда выполнять заказ.
POST /v1/payments/link)Ваш сервер → создаёт ссылку → покупатель открывает в Kaspi
↓ покупатель оплачивает
Ваш сервер ← xpayment (webhook) ← Kaspi (меняет статус)
Шаг 6 из 6 — без вебхука вы не узнаете, что покупатель перешёл по ссылке и оплатил.
| Polling (GET) | Webhook (POST) | |
|---|---|---|
| Задержка | До 15 сек (интервал вашего поллера) | Секунды после оплаты |
| Нагрузка | Постоянные лишние запросы | Только при событии |
| Масштаб | Сложно при тысячах платежей | Не зависит от объёма |
| Пропуск событий | Можно пропустить при сбое | Повтор при ошибке |
Polling подходит как резервный механизм. Вебхук — как основной.
xpayment использует свой внутренний поллер (каждые 15 сек) для опроса Kaspi — и именно тогда срабатывает доставка вебхука на ваш сервер.
localhost не подойдёт.В боковом меню личного кабинета нажмите «Вебхуки» (или перейдите по адресу /app/webhooks). Вы увидите список существующих подписок (пустой, если вебхуков ещё нет).
Нажмите кнопку «Создать вебхук». Откроется модальное окно с двумя вкладками:
Если вы используете одну из платформ-партнёров xpayment (например, tec.delivery) — выберите её карточку. URL и настройки заполнятся автоматически.
Заполните поля самостоятельно:
| Поле | Описание |
|---|---|
| URL | Полный HTTPS-адрес вашего эндпоинта. Например: https://api.myshop.kz/webhooks/xpayment |
| Описание | Необязательное название — для вашего удобства |
| Секрет | Случайная строка для подписи запросов (необязательно, но рекомендуется) |
Отметьте галочками, о каких событиях вы хотите получать уведомления. Рекомендуем выбрать все 5 событий — так ваш сервер всегда будет в курсе любых изменений:
payment.completed — платёж прошёл ✅payment.cancelled — платёж отменён ❌payment.failed — платёж не прошёл ⚠️payment.refunded — возврат выполнен 🔄payment.refund_failed — возврат не удался ⚠️Нажмите «Создать». Вебхук появится в списке со статусом «Активен». С этого момента xpayment будет отправлять POST-запрос на ваш URL при каждом выбранном событии.
Совет: Для тестирования можно использовать сервисы вроде webhook.site — они дают временный публичный URL и показывают входящие запросы в реальном времени.
На диаграмме ниже показан интерфейс создания вебхука в xpayment:
xpayment поддерживает 6 типов событий — по одному для каждого возможного исхода платежа или возврата.
payment.completedКогда: Покупатель нажал «Оплатить» в приложении Kaspi и транзакция прошла успешно.
Что делать на вашем сервере:
Это главное событие — именно его ждёт большинство интеграций.
payment.expiredКогда: Покупатель не оплатил QR-код до истечения его срока действия (обычно 15 минут).
Что делать:
payment.cancelledКогда: Платёж был отменён — либо вашим сервером (через POST /v1/payments/{id}/cancel), либо покупатель нажал «Отклонить счёт» в Kaspi.
Что делать:
payment.failedКогда: Kaspi отклонил платёж — недостаточно средств, неверный номер телефона, технический сбой на стороне Kaspi.
Что делать:
payment.refundedКогда: Возврат по завершённому платежу был успешно выполнен через POST /v1/refunds.
Что делать:
payment.refund_failedКогда: Попытка возврата завершилась ошибкой — например, Kaspi не смог провести обратную транзакцию.
Что делать:
refund_id для расследования| Событие | Триггер | Действие |
|---|---|---|
payment.completed |
Покупатель оплатил | Выполнить заказ |
payment.expired |
QR-код истёк | Освободить резерв, предложить новый платёж |
payment.cancelled |
Отменён вами или покупателем | Освободить резерв |
payment.failed |
Kaspi отклонил платёж | Попросить повторить |
payment.refunded |
Возврат прошёл | Обновить статус заказа |
payment.refund_failed |
Возврат не прошёл | Ручная проверка |
На диаграмме ниже показан путь от смены статуса в Kaspi до доставки вебхука на ваш сервер:
Когда событие срабатывает, xpayment отправляет POST-запрос на ваш URL с таким телом:
{
"event": "payment.completed",
"event_version": "1",
"delivery_id": "c09a3647-7822-4899-8401-3857cbd5bf68",
"attempt_number": 1,
"source": "xpayment",
"payment_id": "65cd0d84-5f66-4763-94d2-1b8e1ba68347",
"merchant_order_id": "order-5512",
"created_at": "2026-04-17T10:32:00+05:00"
}
Поля:
| Поле | Тип | Описание |
|---|---|---|
event |
string | Тип события, например payment.completed |
event_version |
string | Версия схемы payload (сейчас 1) |
delivery_id |
string | Уникальный ID доставки — используйте для идемпотентности |
attempt_number |
int | Номер попытки доставки, начинается с 1 и растёт при повторах |
source |
string | Всегда xpayment |
payment_id |
string | ID платежа, к которому относится событие |
merchant_order_id |
string | Ваш ID заказа (отсутствует, если вы его не задавали) |
created_at |
string | Время события (ISO 8601) |
xpayment отправляет следующие HTTP-заголовки:
POST /webhooks/xpayment HTTP/1.1
Content-Type: application/json
User-Agent: Go-http-client/2.0
X-xPayment-Signature: aeafc0dcd86de4d4d376f525be2b1175520c2818c93938ec8d069957c63a373b
X-xPayment-Signature — это HMAC-SHA256 от тела запроса (см. раздел Безопасность ниже).
Если ваш эндпоинт вернул не-2xx статус (например, 500 Internal Server Error или 503 Service Unavailable), xpayment пометит доставку как неудачную и попытается повторить её позже (всего до 3 попыток; attempt_number увеличивается каждый раз).
Рекомендации:
200 OK как можно быстрее — обработку ведите асинхронно5xx просто потому что ещё не обработали событие200Иногда одно и то же событие может быть доставлено дважды — при повторе после тайм-аута или сбоя сети. Защита:
delivery_id — сохраняйте полученные delivery_id. Если такой delivery_id уже обработан — пропустите.payment_id — ID платежа тоже уникален. Если платёж со статусом COMPLETED уже записан — не выполняйте заказ повторно.// Пример проверки идемпотентности
const deliveryId = payload.delivery_id
if (await alreadyProcessed(deliveryId)) {
return res.status(200).json({ ok: true }) // acknowledge without processing
}
await markAsProcessed(deliveryId)
await fulfillOrder(payload.merchant_order_id)
Каждая доставка вебхука подписывается. xpayment вычисляет HMAC-SHA256 от точного «сырого» тела запроса с ключом — вашим секретом подписки (secret) — и отправляет результат в виде hex в нижнем регистре в заголовке X-xPayment-Signature (чистый hex — без префикса sha256=).
Чтобы доверять входящему вебхуку:
HMAC-SHA256(secret, raw_body) и сравните с X-xPayment-Signature, используя сравнение за постоянное время. Подписывайте байты ровно в том виде, в каком они пришли — не сериализуйте заново распарсенный JSON, иначе пробелы и порядок ключей изменят хеш. Полные примеры кода (Node, Go, Python) — в статье Безопасность вебхуков и секрет.payment.completed