Три шага: запрос → ответ → покупатель платит
Шаг 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-ключи); полный ключ показывается только один раз при создании — подробнее в статье «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-счёт. Счёт доступен к оплате 24 часа. После оплаты вам приходит вебхук invoice.status_changed со статусом paid — обычно в течение 10–20 секунд.
Какие статусы проходит счёт?
| Статус | Что значит |
|---|---|
processing |
Принят, выставляется в Kaspi (асинхронно, с автоповторами) |
pending |
Выставлен, ждёт оплаты (до 24 часов) |
paid |
Оплачен — финальный «хороший» статус |
cancelled |
Отменён (вами через API или покупателем) |
expired |
Истекли 24 часа без оплаты |
error |
Выставить не удалось; в вебхуке будет error_code с причиной |
cancelling |
Отмена в процессе (прод: 202 на запрос отмены); вебхука на этот статус нет |
partially_refunded |
Был частичный возврат |
Вебхуки приходят на переходы в pending, paid, cancelled, expired, error, partially_refunded. На технические статусы processing и cancelling вебхуков не бывает.
«Странные» переходы статусов, которые НЕ баг
Это самый важный раздел — он экономит обращения в поддержку. Все переходы ниже легитимны, закладывайте их в код:
processingдольше 60 минут — не зависание. Если Kaspi временно ограничил частоту запросов (троттлинг), система выдерживает паузы и повторяет выставление — до 200 попыток. Легитимная очередь кассира может держать счёт вprocessingбольше часа. Не пересоздавайте счёт вprocessing— получите два живых счёта и двойную оплату.cancelled→paidиexpired→paid. Покупатель оплатил в последний момент, и оплата «выиграла гонку» у отмены/истечения. Придёт корректирующий вебхукpaid— засчитайте оплату.error→pending. Система сверилась с Kaspi и обнаружила, что счёт всё-таки выставлен, — придёт корректирующий вебхук.error→paidневозможен. Если счёт вerror, без промежуточногоpendingоплаты не будет.- Детект оплаты у «спящей» организации — до ~10 минут. Частота сверки адаптивная: у активно продающих организаций оплата видна за 10–30 секунд, у организации после долгой паузы первая оплата может подтянуться в пределах 10 минут.
Как не выставить два счёта за один заказ?
Передавайте 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 | Уточнить номер у покупателя; счёт на этот номер невозможен |
| Ошибка авторизации кассира | Привязка кассира разорвалась | Переподключить за 1 минуту: Настройки → «Авторизация Kaspi» |
kaspi_throttled |
Kaspi ограничил частоту | Подождать 2–3 минуты; система замедлится сама |
network_unavailable |
Kaspi временно недоступен | Повторить через 1–2 минуты |
unknown_error |
Попытки исчерпаны без диагноза | Написать в поддержку с id счёта |
Важно: если статус уже error — система свои повторы исчерпала, счёт финален. «Повторить» = создать новый счёт.
Частые ошибки
- Поллить
GET /invoices/{id}в цикле вместо вебхука. Работает, но это лишняя нагрузка и задержка; рекомендуемый канал — вебхук. Лимит API — 200 запросов/мин на ключ. - Пересоздавать счёт, пока он в
processing. Получите два живых счёта. Ждите терминального статуса или используйте идемпотентность. - Передавать номер с
+7или 10 цифр. Формат строго8XXXXXXXXXX(11 цифр, первая — 8). В ответах API номер возвращается нормализованным (77001234567) — это нормально. - Считать
processingв ответе ошибкой. Это штатный ответ: выставление асинхронное. - Не передавать
external_order_id_idempotency. При сетевом таймауте ваш повторный запрос создаст второй счёт — покупатель получит два push. - Ждать оплату в песочнице. В тестовом режиме счета в Kaspi не уходят — push не приходит. Переключите «Рабочий режим» в Настройках.
Вопросы и ответы
Сколько живёт счёт по номеру телефона?
24 часа с момента выставления. Не оплачен за 24 часа — перейдёт в expired (придёт вебхук). QR-счёт — отдельный формат со временем жизни 5 минут: «QR-счёт: TTL и лимиты».
Через сколько после оплаты я узнаю о ней?
Обычно за 10–20 секунд приходит вебхук paid. У организации, которая долго не выставляла счета, первая сверка может занять до ~10 минут.
Что будет, если у покупателя нет Kaspi?
Счёт уйдёт в error с кодом client_not_found. Заранее проверить номер можно запросом POST /clients/check (лимит 60/мин на ключ).
Можно ли отменить выставленный счёт?
Да: POST /invoices/{id}/cancel — только для pending/processing. В рабочем режиме ответ 202, отмена асинхронная; если покупатель успел оплатить, придёт error с invoice_already_paid или счёт останется оплаченным.
Обязательно ли указывать amount?
Либо amount, либо корзина cart_items (тогда сумма считается по позициям каталога): «Счета с корзиной (cart_items)».
Счёт висит в processing уже час — это зависание?
Чаще всего нет: при троттлинге со стороны Kaspi система замедляется и повторяет — хвост очереди легитимно живёт больше 60 минут. «Зависшие» счета контролируются автоматикой: мёртвый процесс финализируется в error с осмысленным кодом и вебхуком.