Три шага: запрос → ответ → покупатель платит
Шаг 1. Отправьте запрос
curl -X POST https://api.apipay.kz/api/v1/invoices \
-H "X-API-Key: ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "87001234567",
"amount": 5000,
"description": "Оплата заказа №123",
"external_order_id": "order-123",
"external_order_id_idempotency": "order-123"
}'
Номер — 11 цифр, начинается с 8: 8XXXXXXXXXX. API-ключ берётся в кабинете apipay.kz (Настройки → «Подключение»); полный ключ показывается только один раз при создании — подробнее в статье «API-ключ и вебхук-секрет».
Шаг 2. Получите ответ 201 со статусом processing
{
"id": 1,
"amount": "5000.00",
"status": "processing",
"phone": "77001234567",
"created_at": "2026-07-02T10:25:00+06:00"
}
processing означает «принято в работу»: выставление счёта в Kaspi происходит асинхронно, в фоне. Ничего опрашивать в цикле не нужно — итоговый статус придёт вебхуком (настройка — «Как настроить вебхуки ApiPay»). Для отладки текущее состояние можно посмотреть запросом GET /invoices/{id} — там же видны поля last_kaspi_error_code и last_kaspi_error_message, если Kaspi отвечал ошибкой.
Шаг 3. Что видит покупатель
Покупателю приходит push-уведомление в приложении Kaspi: счёт с вашим описанием и суммой, кнопка «Оплатить». Деньги идут напрямую на ваш Kaspi-счёт.
Какие статусы проходит счёт?
Путь счёта: processing → pending → paid / cancelled / expired; если выставить не удалось — error. Вебхуки приходят на переходы в pending, paid, cancelled, expired, error, partially_refunded; на технические processing и cancelling вебхуков не бывает.
Не пересоздавайте счёт, пока он в processing: получатся два живых счёта, и покупатель может оплатить оба. Бывают законные «странные» переходы — cancelled → paid, expired → paid, error → pending, — их надо заложить в обработчик. Что означает каждый статус и как обрабатывать поздний paid — «Жизненный цикл счёта».
Как не выставить два счёта за один заказ?
Передавайте external_order_id_idempotency (до 191 символа, уникален в пределах организации — удобно класть туда ID заказа). Повторный запрос с тем же значением вернёт 409 duplicate_idempotency_key с invoice_id и status уже существующего счёта — дубль не создастся, даже при гонке двух параллельных запросов.
Исключение: если прежний счёт уже мёртв (expired, cancelled, error), повторный запрос с тем же ключом создаст новый счёт — это штатное перевыставление неоплаченного заказа. Для живых статусов (processing, pending, paid, partially_refunded) всегда будет 409.
Почему счёт ушёл в error?
В вебхуке и в GET /invoices/{id} будет машиночитаемый error_code. Самые частые:
| error_code | Причина | Что делать |
|---|---|---|
client_not_found |
Номер не зарегистрирован в Kaspi | Уточнить номер у покупателя; счёт на этот номер невозможен |
session_transient |
Проблема с авторизацией кассира Kaspi | Создать счёт заново; если повторяется — переподключить кассира: Настройки → «Авторизация Kaspi» |
kaspi_throttled |
Kaspi ограничил частоту запросов кассира | Счёт финален, автоповторов нет: подождать 2–3 минуты, снизить темп выставления и создать новый счёт |
network_unavailable |
Kaspi временно недоступен | Повторить через 1–2 минуты |
unknown_error |
Попытки исчерпаны без диагноза | Написать в поддержку с id счёта |
Важно: статус error терминален — по такому счёту сервис больше ничего не сделает. «Повторить» = создать новый счёт.
Вопросы и ответы
Что будет, если у покупателя нет Kaspi?
Счёт уйдёт в error с кодом client_not_found. Заранее проверить номер можно запросом POST /clients/check (лимит 60/мин на ключ).
Можно ли отменить выставленный счёт?
Да: POST /invoices/{id}/cancel — только для pending/processing и только для счёта на номер телефона. QR-счёт отменить нельзя — запрос отвечает 409 qr_cancel_unsupported, статус не меняется, QR гаснет сам (см. «QR-счёт: TTL и лимиты»). В рабочем режиме ответ 202 и статус cancelling. После 202 счёт отменённым не считайте — исходов три: вебхук cancelled; вебхук error с invoice_already_paid; либо тихий откат в pending без вебхука (обычно «уже оплачен») — реальный статус в этом случае принесёт следующий вебхук синхронизации или GET /invoices/{id}.
Почему сумма с копейками не проходит?
Счёт на номер телефона принимает только целые тенге: дробная сумма отбивается сразу с 422 amount_must_be_whole_tenge, и счёт не создаётся. Проверяется и голая amount, и итог корзины после скидок — discount_percentage считается построчно, поэтому даже при целых ценах итог может стать дробным (999 ₸ со скидкой 10 % дают 899.10). Округлите цены позиций или процент скидки. Нужны копейки — выставляйте счёт через POST /invoices/qr: там суммы с тиынами принимаются. То же правило действует на списания по подписке и на оплату с печатного листа по номеру телефона — там дробная сумма даёт статус error.
Оплата не приходит — что проверить?
В песочнице счета в Kaspi не уходят и push не приходит: для реальных оплат включите «Рабочий режим» в Настройках (см. «Песочница и рабочий режим»).