> Источник: https://apipay.kz/guides/podpiski-apipay · Обновлено: 2026-10-24 · apipay.kz
> ApiPay — независимый сервис приёма платежей поверх вашего Kaspi Pay.

# Подписки ApiPay: есть ли автосписание с покупателя?

**TL;DR.** Автосписания с покупателя нет: ApiPay сам деньги не списывает, а по расписанию выставляет обычный счёт Kaspi, и каждую оплату покупатель подтверждает в приложении Kaspi. Так работают рекуррентные платежи через ApiPay: подписка или ежемесячный автоплатёж в кабинете ApiPay сами создают счёт в нужный день и перевыставляют его при неоплате. Для B2B механика та же: счёт приходит на номер телефона, и в приложении Kaspi плательщик сам выбирает, с какого счёта оплатить — со своего или со счёта организации, если у него есть к нему доступ (подробнее — на странице [Рекуррентные платежи Kaspi](/recurring-payments-kaspi)). У подписки два режима неоплаты: с `bill_until_paid: true` счёт выставляется, пока не оплатят, и подписка не истекает; без него (так по умолчанию в API) — до 3 счетов за период, затем льготный период и `expired`.

## Как работает подписка ApiPay?

Цикл выглядит так:

1. Вы создаёте подписку: номер телефона покупателя + период + сумма.
2. В расчётную дату система сама создаёт **обычный счёт Kaspi** — покупателю приходит push в приложение Kaspi.
3. Покупатель нажимает «Оплатить» — деньги, как всегда, идут напрямую на ваш Kaspi-счёт. Вам приходит вебхук `subscription.payment_succeeded`.
4. Дата следующего выставления сдвигается на период — и так по кругу.

## Как создать подписку через API?

```bash
curl -X POST "https://api.apipay.kz/api/v1/subscriptions" \
  -H "X-API-Key: ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "87001234567",
    "amount": 15000,
    "billing_period": "monthly",
    "billing_day": 1,
    "billing_time": "13:00",
    "total_cycles": 12,
    "description": "Абонемент, тариф Стандарт"
  }'
```

Параметры и границы: `billing_period` — `daily | weekly | biweekly | monthly | quarterly | yearly`; `amount` — от 100 до 1 000 000 ₸ и **только целые тенге** (дробную сумму создание подписки примет, но каждое списание уйдёт в `error` с `amount_must_be_whole_tenge`); итог `cart_items` — те же 100–1 000 000 ₸ в целых тенге по текущим ценам каталога, иначе `422` (`cart_total_out_of_range` / `cart_total_not_whole_tenge`) сразу при создании и изменении; `max_retry_attempts` — сколько счетов выставить за период при неоплате, 1–10 (по умолчанию 3); `retry_interval_hours` — 1–168 (по умолчанию 24); `grace_period_days` — 1–30 (по умолчанию 3); `description` — до 60 символов (Kaspi показывает покупателю только их), считаются после подстановки макросов — см. раздел «Макросы в описании счёта» ниже; `bill_immediately` — выставить первый счёт сразу при создании; `bill_until_paid` — режим неоплаты, см. «Что происходит, когда покупатель не платит?». Поля `max_retry_attempts`, `retry_interval_hours` и `grace_period_days` действуют только без `bill_until_paid: true`.

### Когда именно выставляется счёт

`billing_day` — 1–28 у месячного, квартального и годового периода; у `weekly` и `biweekly` это **день недели**: 1 — понедельник, 7 — воскресенье (значение больше 7 на этих периодах вернёт `422`). У `daily` поле не используется.

Числом больше 28 день не задаётся: 29-е, 30-е и 31-е есть не в каждом месяце. Если нужен конец месяца, передайте `billing_day_from_end` вместо `billing_day` — `0` означает последний день месяца, `1` — предпоследний. Поля взаимоисключающие: вместе они вернут `422`, а сама опора от конца месяца доступна только месячному, квартальному и годовому периоду. В кабинете обе опоры выбираются одним списком, словами: числа с 1-го по 27-е, «предпоследний день месяца» и «последний день месяца». 28-е число доступно только через API.

`billing_time` — час выставления в формате `ЧЧ:ММ` **по времени Алматы**, окно с 06:00 до 22:00. Не передан — счёт уходит в 13:00.

`first_billing_at` (`ГГГГ-ММ-ДД`, календарь Алматы) задаёт дату **первого** счёта, дальше подписка идёт по расписанию. Поле несовместимо с `bill_immediately`: вместе они вернут `422`. Дата не может быть в прошлом и дальше двух лет вперёд — иначе `422`. Без него первый счёт считается как «дата начала + период».

`total_cycles` (1–600) ограничивает подписку по числу **оплат**: неоплаченная попытка цикл не расходует. Сколько уже внесено, показывает `cycles_paid`. Поле не передано — подписка бессрочна. Когда оплаты закончились, подписка переходит в `expired`, а в вебхуке `subscription.expired` приходит `reason: total_cycles_reached`.

В ответе подписки `next_billing_in_days` — **знаковое**: отрицательное значение означает, что дата списания уже прошла.

**Важно:** ответ `201` на `POST /subscriptions` означает «подписка создана», а не «покупателю уже ушёл счёт». Если вы ждёте немедленный push покупателю — передайте `bill_immediately: true`. Управление: `POST /subscriptions/{id}/pause | resume | cancel` и пропуск периода (`skip-period`, см. раздел «Как пропустить период, если покупатель заплатил другим способом?»), счета подписки — `GET /subscriptions/{id}/invoices`.

Подпискам нужна верифицированная организация: без неё API отвечает `400 Subscriptions require a verified organization`.

### ИИН/БИН покупателя

Необязательное поле `buyer_bin` — ИИН или БИН покупателя, строка ровно из 12 цифр: `"buyer_bin": "012345678901"`. Передавайте его строкой, а не числом, иначе потеряется ведущий ноль. Неверный формат отбивается `422` с ключом `buyer_bin` в `errors{}`.

Номер уходит в каждый следующий счёт подписки. Если у организации подключена Kaspi Касса, он попадёт в чек Kaspi. Kaspi номер не проверяет, поэтому сверьте все 12 цифр.

В `PUT /subscriptions/{id}` без ключа `buyer_bin` номер не меняется, а `null` его стирает. Правка действует на будущие счета, уже выставленные не меняются. Номер возвращается в ответе подписки, в вебхуках его нет.

В кабинете номер задаётся полем «ИИН/БИН покупателя (необязательно)» в мастере автоплатежа и в настройках подписки. Стереть его можно на странице автоплатежа кнопкой «Стереть».

При загрузке автоплатежей из Excel номер — необязательная колонка «ИИН/БИН покупателя». Пустая ячейка уже сохранённый номер не стирает. Задайте столбцу текстовый формат, иначе Excel уберёт ведущий ноль.

## Макросы в описании счёта

В `description` подписки можно поставить макрос — значение подставится в каждый счёт.

Подписка хранит шаблон. Значения подставляются в момент выставления каждого счёта.

| Макрос | Что подставится | Где работает |
|---|---|---|
| `{today}` | Дата выставления счёта, `дд.мм.гггг`, по времени Алматы | Любая подписка: API и кабинет |
| `{month}` | Месяц услуги строчными: «сентябрь» | Только автоплатёж в кабинете с выбранным месяцем услуги |
| `{year}` | Год месяца услуги | Только автоплатёж в кабинете с выбранным месяцем услуги |

Примеры:

- `Счёт от {today}` → «Счёт от 05.09.2026».
- `Оплата за {month} от {today}` → «Оплата за сентябрь от 05.09.2026».
- `Абонемент на {month} {year}` для счёта от 29.12.2026 за январь → «Абонемент на январь 2027».

**`{today}` — дата выставления счёта, а не дата оплаты.** Повторный счёт после неоплаты получает свою дату.

**`{month}` — месяц услуги, а не месяц списания.** Счёт 29 сентября за октябрь покажет «октябрь».

Через API (`POST /subscriptions`, `PUT /subscriptions/{id}`) работает `{today}`. Макросы `{month}` и `{year}` есть только у автоплатежа, заведённого в кабинете с выбором месяца услуги («за какой месяц»). У остальных подписок они вернут `422`.

Неизвестный макрос или другой регистр (`{Today}`) вернёт `422` по полю `description`. Текст в фигурных скобках на кириллице макросом не считается и уходит как есть.

Лимит в 60 символов считается по тексту после подстановки: `{today}` превращается в 10 знаков.

В ответе подписки `description` остаётся шаблоном. Готовый текст приходит в `description` счёта и в вебхуке `invoice.status_changed`.

⚠️ Если в уже сохранённом описании буквально стоит `{today}`, в новые счета туда подставится дата. `{month}` и `{year}` подставятся так же, но только у автоплатежа с выбранным месяцем услуги — у остальных подписок они останутся текстом. Сохранённые описания не проверяются заново, пока их не меняют.

Если описание было длиной почти 60 символов, после подстановки его конец покупатель может не увидеть. Сохраните описание заново — тогда длина проверится.

Макросы не нужны — ничего делать не надо: описания без них работают как раньше.

## Что происходит, когда покупатель не платит?

Если счёт не оплачен (истёк через 24 часа или отменён), вам приходит `subscription.payment_failed` с причиной (`Invoice expired` / `Invoice cancelled`) и номером попытки.

Счёт система перевыставляет сама. Вручную ничего пересоздавать не нужно (и не надо: получите два параллельных счёта).

Что будет дальше, зависит от режима — поля `bill_until_paid`.

| | `bill_until_paid: true` | не передано или `false` |
|---|---|---|
| Истёкший счёт | Перевыставляется сразу, пока не наступил момент следующего планового списания | Перевыставляется сразу, пока счетов за период меньше `max_retry_attempts` |
| Счёт отменён не самим плательщиком (например, не хватило денег) | Период закрывается без повтора, следующий счёт — в следующем периоде | Повтор через `retry_interval_hours` (по умолчанию 24 ч) |
| Неоплата затянулась | Начинается новый период с новым счётом, подписка не истекает | Grace-период, затем `expired` |
| Номер не зарегистрирован в Kaspi (`client_not_found`) | Подписка отменяется | Обычные повторы, затем grace |

**Через API режим выключен по умолчанию**: чтобы подписка не истекала из-за неоплаты, передайте `bill_until_paid: true`.

В кабинете мастер заводит подписку на другой период — день, неделю, две недели, квартал или год — сразу с этим режимом. Выключить его можно потом: галка «Выставлять счёт, пока не оплатят» во вкладке «Настройки» подписки. Подписки на другой период, загруженные из таблицы, создаются без режима — включить его можно там же.

У ежемесячного автоплатежа, который кабинет создаёт мастером, этого режима нет: там задаётся, сколько дней повторять неоплаченный счёт (по умолчанию 3), после чего выставление останавливается и возобновляете его вы.

### Режим «выставлять, пока не оплатят»

Подписка с `bill_until_paid: true` из-за неоплаты не истекает. Grace-периода и `subscription.grace_period_started` в этом режиме нет.

Неоплаченный период просто сменяется следующим — со своим счётом. Долг за прошлый период новый счёт не включает. Неоплаченные периоды видны в истории счетов подписки и по `subscription.payment_failed` — если долг нужно взыскать, учитывайте его у себя.

Счёт, отменённый не самим плательщиком (например, у него не хватило денег), и счёт в статусе `error` закрывают текущий период без повтора. `subscription.payment_failed` при этом приходит как обычно. Исключение — ошибка на стороне сервиса: период она не закрывает, выставление повторится само.

Если номер плательщика не зарегистрирован в Kaspi (`error_code = client_not_found`), подписка отменяется. Приходит `subscription.cancelled` с `reason: payer_error`, `invoice_id` и `error_code`, а `subscription.payment_failed` по этому счёту не приходит.

`max_retry_attempts` в этом режиме передавать нельзя: вместе с `bill_until_paid: true` он вернёт `422` по полю `max_retry_attempts`. На `PUT /subscriptions/{id}` отказ придёт, если режим уже включён у подписки, даже когда число равно текущему. Передаёте в `PUT` весь объект подписки из ответа — уберите из него `max_retry_attempts` или отправьте `null` («не менять»).

Режим можно включить и выключить через `PUT /subscriptions/{id}`. Смена режима обнуляет счётчик неудачных попыток `failed_attempts`. Если подписка в grace-периоде, включение режима вернёт её к выставлению счетов.

### Режим с лимитом счетов

Без `bill_until_paid: true` действует лестница:

1. **Система сама перевыставляет счёт** — пока счетов за период меньше `max_retry_attempts` (по умолчанию 3), с интервалом `retry_interval_hours` (по умолчанию 24 ч). Исключение одно: если счёт **истёк**, он перевыставляется сразу — покупатель уже израсходовал весь срок его жизни, и ждать сверх этого нечего.
2. **Попытки исчерпаны** → начинается **grace-период** (`subscription.grace_period_started`, по умолчанию 3 дня): новые счета в это время не выставляются, доступ покупателю вы пока не отключаете, а любая его оплата немедленно возвращает подписку в норму.
3. **Grace истёк** → `subscription.expired`: счета по этой подписке больше не выставляются.

Покупатель вернулся? Истёкшую подписку можно возобновить: в кабинете — «Возобновить» в меню автоплатежа; подписку, созданную через API, — запросом `POST /subscriptions/{id}/resume`. Периодичность и расписание сохранятся, счётчик неудачных попыток обнулится, а пропущенные периоды не доначисляются.

Подписку, которая получила все оплаты `total_cycles`, возобновить нельзя — для неё создайте **новую**.

### Явный отказ покупателя

Отказ действует в обоих режимах. Если покупатель сам отклонил счёт в Kaspi, подписка отменяется сразу: приходит `subscription.cancelled` с `reason: payer_refused`, повторов и grace не будет.

Нехватка денег на счёте отказом **не** считается. Отклонение покупки на стороне Kaspi — тоже.

Отменённую так подписку возобновить нельзя, как и после `cancel`: если покупатель передумал, создайте новую.

Отдельная причина тишины — ваш собственный тариф ApiPay. Если тариф не оплачен (`403 tariff_inactive`) или включено ограничение по дневному лимиту (`429 tariff_limit_reached`), автосписание пропускает цикл молча: счёт покупателю не выставляется, дата следующего списания не сдвигается, счётчик неудачных попыток не растёт, вебхука об этом нет. См. [Лимит счетов по тарифу](/guides/limit-schetov-po-tarifu).

**Пропущенные за простой периоды сгорают.** Когда причина уходит, подписка выставляет один счёт за текущий период и встаёт на ближайшую будущую дату — покупателю не приходит пачка счетов за всё время тишины.

Если списания стояли **дольше недели**, сами они не возобновятся: в кабинете, в разделе «Автоплатежи», появится кнопка «Запустить подписки» — нажать её должен человек. Там же кабинет называет причину остановки, если она ещё не устранена.

Пауза устроена мягче: `pause` останавливает выставление, `resume` продолжает **от текущего момента** — пропущенные периоды не доначисляются, покупателю не прилетит «счёт за три месяца тишины». `cancel` — безвозвратен: отменённую подписку не возобновить, нужна новая.

## Как пропустить период, если покупатель заплатил другим способом?

Если покупатель рассчитался за период наличными, переводом или иначе, пропустите этот период: он закроется без оплаты, а подписка продолжит работать. Попытки и льготный период сбрасываются, следующий счёт придёт по расписанию.

**В кабинете:** раздел «Автоплатежи» → «⋯» в строке автоплатежа → «Пропустить период». Окно покажет, какой период будет пропущен и когда придёт следующий счёт.

**Через API** — в два шага:

1. `POST /subscriptions/{id}/skip-period/preview` — ничего не меняет и показывает объект `skip`: какой период будет пропущен (`billing_period_start`, `billing_period_end`), его счёт (`invoice` или `null`) и когда придёт следующий (`next_billing_at`). Покажите это человеку.
2. `POST /subscriptions/{id}/skip-period` с `billing_period_start` из превью:

```bash
curl -X POST "https://api.apipay.kz/api/v1/subscriptions/10/skip-period" \
  -H "X-API-Key: ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"billing_period_start": "2026-03-01"}'
```

Повтор с тем же `billing_period_start` безопасен: второй период не пропустится, ответ придёт с `skip.replayed: true`.

Отказы приходят с кодом `409`:

- `skip_period_changed` — пока человек подтверждал, период сменился (например, вышел новый счёт). В ответе свежий `skip`: покажите его и подтвердите заново, вслепую не повторяйте.
- `subscription_busy` — по подписке прямо сейчас выставляется счёт. Повторите через минуту с тем же значением.
- `subscription_not_active` — подписка на паузе, отменена или истекла: пропустить можно только период активной подписки.
- `subscription_cycles_exhausted` — получены все оплаты `total_cycles`, пропускать нечего.

**Что с уже выставленным счётом.** ApiPay отправляет в Kaspi запрос на его отмену, итог придёт вебхуком `invoice.status_changed`. Если Kaspi отмену не примет, вебхука не будет — счёт останется доступен покупателю.

**Если покупатель всё же оплатит этот счёт**, период засчитается оплаченным: придёт `subscription.payment_succeeded`. Если он уже рассчитался с вами другим способом, лишнюю оплату верните сами — возвратом по счёту (`POST /invoices/{id}/refund`). Не откладывайте: окно возврата задаёт Kaspi — ориентир около 14 дней с оплаты, подробнее — «[Возвраты Kaspi через API](/guides/vozvraty-kaspi-cherez-api)».

**Покупатель не становится должником.** В истории платежей подписки у периода статус «Пропущен» (`skipped`). Если счёт за период ещё не выставлялся, у строки нет счёта: `invoice_id` и `amount` равны `null`. В статистику подписки пропуски не входят.

**Пропуск — не оплата.** Подписка с `total_cycles` проработает на период дольше: цикл расходует только оплата.

Не путайте пропуск с паузой. После `resume` периоды паузы просто не доначисляются. Пропуск закрывает один конкретный период, подписка при этом не останавливается, а в истории у периода появляется статус `skipped`.

## Какие вебхуки присылает подписка?

Вебхуки подписок приходят на тот же URL, что и счета (настройка — «[Настройка вебхуков ApiPay](/guides/nastroyka-webhookov-apipay)»):

| Событие | Когда | Доп. поля (в корне payload) |
|---|---|---|
| `subscription.created` | Подписка создана | — |
| `subscription.payment_succeeded` | Счёт подписки оплачен | `invoice_id`, `amount`, `paid_at` |
| `subscription.payment_failed` | Счёт истёк, отменён или ушёл в `error` | `invoice_id`, `amount`, `reason`, `attempt_number`, `error_code` |
| `subscription.grace_period_started` | Ретраи исчерпаны (только без `bill_until_paid: true`) | `grace_period_days`, `expires_at` |
| `subscription.expired` | Grace истёк либо закончились оплаты `total_cycles` | во втором случае `reason: total_cycles_reached` |
| `subscription.cancelled` | Отмена: вашим запросом, явным отказом покупателя либо, в режиме `bill_until_paid`, по `client_not_found` | при отказе покупателя — `reason: payer_refused`, `invoice_id`; по `client_not_found` — `reason: payer_error`, `invoice_id`, `error_code` |
| `subscription.paused` / `resumed` | Соответствующие действия | — |
| `subscription.period_skipped` | Период закрыт без оплаты — в кабинете или через `skip-period` | `billing_period_start`, `billing_period_end`, `cancelled_invoice_id` (счёт, отмену которого запросил пропуск, или `null`) |

Инженерные нюансы (трек D):

- **Дедупликация обязательна:** у событий подписок нет защиты от дублей — дедуплицируйте по `(event, subscription.id, invoice_id)`, а `subscription.period_skipped` — по `(event, subscription.id, billing_period_start)`: `invoice_id` в нём нет.
- **Ретраев в логах не ищите:** события `subscription.*` не попадают в webhook-логи кабинета и не имеют ручного перезапуска — отвечайте `200` быстро и обрабатывайте асинхронно.
- **Счёт подписки в статусе `error`** — это тоже неуспешная попытка: приходит `subscription.payment_failed`, в payload добавляется `error_code`. Если ошибка на стороне сервиса (например, оборвалась Kaspi-сессия), попытка не засчитывается и период сохраняется — следующая попытка будет позже. Если причина в плательщике (`client_not_found` — у номера нет Kaspi), без `bill_until_paid: true` идут обычные ретраи и затем grace, а с ним подписка отменяется (`subscription.cancelled`, `reason: payer_error`).
- **Позиция каталога, создание которой брошено, ломает списание.** Если в корзине подписки стоит позиция с `sellable: false` (`status: failed` при `operation: create`), счёт не выставится, и прогон засчитается **неудачным**: `failed_attempts` растёт, а без `bill_until_paid: true` дальше ретраи, grace и `expired`. Это не отсрочка — в отличие от неоплаченного тарифа, где цикл просто пропускается. Позицию нужно починить сразу, а без `bill_until_paid: true` — до исчерпания `max_retry_attempts`: работу по строке возобновляет `PATCH /catalog/{id}`, в кабинете — кнопка «Повторить».
- **Цена позиции каталога влияет на действующие подписки.** Итог `cart_items` проверяется при создании и изменении подписки, но при каждом списании считается заново по текущим ценам каталога. Если после правки цены итог получится с тиынами, счёт не выставится: списание уйдёт в `error` с `amount_must_be_whole_tenge`. Меняя цены позиций, проверьте подписки, в корзине которых они стоят.

## Вопросы и ответы

**Почему в подписке нет автосписания?**
Оплату по номеру телефона в Kaspi всегда подтверждает сам покупатель: автосписания с карты, как у классической банковской подписки, в Kaspi Pay нет. Клиентам это честно формулируется так: «счёт будет приходить автоматически, оплата в один клик».

**Можно ли изменить сумму действующей подписки?**
Да, через `PUT /subscriptions/{id}` — изменения применяются к будущим счетам. Тем же методом меняются день и час списания и число оплат; дата первого счёта и `bill_immediately` задаются только при создании.

**Во сколько покупателю приходит счёт по подписке?**
По умолчанию счета уходят в 13:00 по времени Алматы. Час выбирается полем `billing_time` в окне с 06:00 до 22:00.

**Можно ли подставить в описание счёта дату или месяц?**
Да, макросами в `description`. `{today}` подставляет дату выставления счёта и работает через API. `{month}` и `{year}` — месяц услуги и его год — есть только у автоплатежа, заведённого в кабинете с выбором месяца услуги; у остальных подписок они вернут `422`.

**Подписка истекла (`expired`) — я потеряю историю?**
Нет: подписка и все её счета остаются в кабинете и API. Истёкшую после льготного периода подписку можно возобновить (`POST /subscriptions/{id}/resume` или «Возобновить» в кабинете). Если получены все оплаты `total_cycles`, создайте новую подписку.

**Покупатель заплатил наличными — как не выставлять ему счёт за этот период?**
Пропустите период: в кабинете — «Пропустить период» в меню автоплатежа, через API — превью `POST /subscriptions/{id}/skip-period/preview`, затем `POST /subscriptions/{id}/skip-period`. Подписка продолжит работать. Уже выставленный счёт ApiPay отправит на отмену в Kaspi; если Kaspi отмену не примет, счёт останется доступен покупателю.

Смотрите также: [Как создать счёт Kaspi по номеру](/guides/kak-sozdat-schet-kaspi-po-nomeru) · [Настройка вебхуков ApiPay](/guides/nastroyka-webhookov-apipay) · [Тарифы и комиссия ApiPay](/guides/tarify-i-komissiya-apipay) · [Рекуррентные платежи Kaspi](/recurring-payments-kaspi) · пиллар «[Как принимать оплату Kaspi через API](/kaspi-api)».

---

ApiPay — независимый сервис и не аффилирован с АО «Kaspi Bank». Kaspi и Kaspi Pay — товарные знаки их правообладателя.
База знаний: https://apipay.kz/guides
