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

# Как связать свою CRM с Kaspi-оплатами?

**TL;DR.** Типовая задача — чтобы карточка сделки автоматически двигалась, когда покупатель оплатил Kaspi. Минимальный контракт интеграции — **три вызова**: `POST /invoices` (создать счёт по номеру покупателя, счёт живёт 24 часа), вебхук `invoice.status_changed` (получить `paid` и передвинуть сделку), `GET /invoices/{id}` (подстраховка-сверка). Обязательно передавайте `external_order_id_idempotency` = ID вашей сделки: повторный запрос вернёт `409` вместо второго счёта — это ваша защита от дублей при ретраях CRM. Если CRM несколько — сделайте каждой отдельный API-ключ.

## Как это работает у вас

```
CRM: сделка перешла в «Выставить счёт»
        │
POST /invoices  { phone_number, amount, external_order_id_idempotency: deal_id }
        │  201, status: processing → счёт уходит покупателю push'ем в Kaspi
        │
Покупатель оплачивает (у него 24 часа)
        │
Вебхук invoice.status_changed { status: paid, external_order_id: ... }
        │
CRM: находит сделку по external_order_id → двигает карточку в «Оплачено»
```

Никакого «входа в Kaspi по номеру телефона» из кода — CRM ходит только в REST ApiPay с заголовком `X-API-Key`; сессию с Kaspi держит ApiPay.

## Минимальный контракт интеграции

Весь [Kaspi API](/kaspi-api) для CRM сводится к трём вызовам:

| # | Вызов | Направление | Зачем |
|---|---|---|---|
| 1 | `POST /invoices` | CRM → ApiPay | Создать счёт: `phone_number` (формат `8XXXXXXXXXX`), `amount`, `description` ≤60, `external_order_id`, `external_order_id_idempotency` |
| 2 | `POST {ваш webhook}` — `invoice.status_changed` | ApiPay → CRM | Единственный триггер движения сделки: `paid` / `cancelled` / `expired` / `error` |
| 3 | `GET /invoices/{id}` | CRM → ApiPay | Сверка/восстановление: если вебхук потерялся или нужен ручной рефреш карточки |

Этого достаточно для «карточка двигается по оплате». Возвраты (`POST /invoices/{id}/refund` — см. [возвраты](/guides/vozvraty-kaspi-cherez-api)) и корзина `cart_items` (при Kaspi ОФД) добавляются потом, по мере надобности.

## Пошаговый сетап

1. **Ключи**: в кабинете apipay.kz создайте API-ключ и вебхук-секрет. Это разные вещи: ключ — для ваших запросов, секрет — для проверки подписи входящих вебхуков ([разница](/guides/api-klyuch-i-webhook-secret)).
2. **Песочница**: прогоните весь цикл в тестовом режиме — там есть simulate-оплата. Переключение в рабочий режим обратимо и ключи не меняет: отдельного sandbox-ключа в ApiPay нет ([детали](/guides/pesochnitsa-i-rabochiy-rezhim)).
3. **Создание счёта из CRM**:

```bash
curl -X POST https://api.apipay.kz/api/v1/invoices \
  -H "X-API-Key: ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "8701XXXXXXX",
    "amount": 45000,
    "description": "Сделка #4812, консультация",
    "external_order_id": "deal-4812",
    "external_order_id_idempotency": "deal-4812"
  }'
```

Ответ — `201` со `status: "processing"`: счёт создаётся асинхронно, Kaspi ещё не вызван. Не пересоздавайте счёт, пока он в `processing`: получатся два живых счёта, и покупатель может оплатить оба.

4. **Приём вебхука**: эндпоинт в CRM, проверка подписи `X-Webhook-Signature: sha256=<hex>` — HMAC-SHA256 по **сырому телу** запроса ([пошагово](/guides/nastroyka-webhookov-apipay)). Отвечайте `2xx` быстро, обработку — в очередь.
5. **Движение сделки**: по `external_order_id` из payload находите сделку; `paid` → «Оплачено», `expired` → «Просрочен, перевыставить», `error` → на разбор менеджеру.
6. **Обработчик должен быть идемпотентным**: ApiPay повторяет доставку недоставленных вебхуков (до 11 попыток с нарастающим интервалом) — повторная доставка `paid` не должна двигать сделку дважды.

## Идемпотентность: главная страховка CRM

CRM ретраят HTTP-запросы, поэтому один заказ может превратиться в несколько счетов. Защита:

- `external_order_id_idempotency` уникален в пределах организации (до 191 символа). Повтор → **`409 duplicate_idempotency_key`** с `invoice_id` и статусом уже существующего счёта — просто используйте его.
- Исключение by design: если прежний счёт уже мёртв (`expired`, `cancelled`, `error`), повторный POST с тем же ключом **создаст новый счёт** — это штатное перевыставление, 409 не будет.
- Лучший ключ — ID сделки/заказа в вашей CRM: `deal-4812`. Ключ на попытку запроса (`deal-4812-retry2`) — антипаттерн, он отключает защиту.

## Несколько CRM или отделов — несколько токенов

Если счета от одного юрлица выставляют две системы (например, CRM продаж и учётная система — для 1С есть [отдельная страница](/kaspi-pay-1c)), не делите один ключ — создайте **отдельный API-ключ на каждую**. У каждого ключа свой вебхук; в payload вебхука поле `source` содержит имя ключа-создателя, так что «чей счёт» всегда видно. Оплата тарифа при этом одна — тариф считается по организации, а не по числу ключей.

## Грабли этого бизнеса

1. **Спам-цикл ретраев.** No-code/ИИ-CRM без идемпотентности умеют выставить сотни счетов за минуты. Аварийный kill-switch — удалить API-ключи в кабинете, затем чинить логику ретраев и включать `external_order_id_idempotency`.
2. **Двойное движение сделки.** Вебхуки доставляются повторно при ретраях — дедупите по `invoice.id` + `status` на своей стороне.
3. **`processing` — технический статус создания.** Штатно он длится секунды. Дольше — либо у организации включён «Режим накопления счетов», либо выставление не доехало до Kaspi, и тогда сервис сам финализирует счёт в `error` с вебхуком. Пока счёт в `processing`, не пересоздавайте его — получатся два живых счёта; смотрите `last_kaspi_error_*` в `GET /invoices/{id}`.
4. **Каталог и скидки.** Счёт по номеру (`POST /invoices`) можно выставить одной суммой `amount` даже у организации с каталогом — `cart_items` нужны только тогда, когда позиции должны попасть в чек Kaspi. Остальные правила корзины и скидок — [cart_items и 422](/guides/scheta-s-korzinoy-cart-items-ofd).
5. **401 после запуска прода.** При переключении режима ключ не меняется, поэтому 401 — это почти всегда перегенерированный ключ (в кабинете или партнёрским эндпоинтом) либо ключ другой организации в конфиге CRM. Сверьте `key_hint` того ключа, что лежит в конфиге.

## Рекурренты: абонементы из CRM

Если CRM ведёт абонементы (спортзал, подписка на сервис, рассрочка), не пишите свой крон — используйте подписки ApiPay. Подписка = авто-**выставление** счетов (не автосписание с карты — в Kaspi его нет): система сама создаёт счёт в срок, клиент подтверждает оплату push'ем, а по счёту идут обычные invoice-вебхуки. В `external_subscriber_id` кладите ID клиента в CRM.

Один нюанс именно для CRM: события `subscription.*` **не пишутся в webhook-логи** и не имеют ручного retry — дедупьте по `(событие, subscription.id)` и не теряйте. Параметры, полный жизненный цикл и цены — в статье [Подписки ApiPay](/guides/podpiski-apipay) и на витрине [Рекуррентные платежи Kaspi](/recurring-payments-kaspi).

## Филиалы и кассиры

Одна организация может иметь несколько касс/торговых точек. Кассу для счёта выбираете полем `kaspi_connection_id` в `POST /invoices` (по умолчанию — основная касса). Если активных касс больше одной, основная не назначена и параметр не передан — вернётся `422 connection_ambiguous`: передайте явный `kaspi_connection_id`.

Управлять кассирами прямо из CRM можно через `/connections*` — но только если у ключа включён флаг `can_manage_cashiers` (включает **владелец** в кабинете; без флага — `403 cashier_management_disabled`). Сама процедура подключения и переавторизации кассира — «[Подключение кассира Kaspi](/guides/podklyuchenie-kassira-kaspi)». Разделение отчётности и счетов по точкам — [Раздельная отчётность по точкам](/guides/razdelnaya-otchetnost-po-tochkam).

## Мониторинг: «касса слетела» и дашборд

Фоновый мониторинг CRM строится на `GET /account/health`: поля `connection.session_status` (`active` / `expired` / `error`) и `connection.needs_reauth` — так CRM детектит «слетела Kaspi-сессия» (вебхука на это **нет**); при плохом статусе — алерт менеджеру и запуск переавторизации. Там же `tariff` (дни до конца) и `invoicing.accumulating`. Для дашборда — `GET /invoices/stats` (`period` `today`/`week`/`month`/`year` **или** `start_date`+`end_date`) → виджеты «оплачено за период», «конверсия» (`conversion_rate`), «в ожидании» (включает `processing`). `GET /status` — liveness без авторизации.

Важно: **тайм-зоны.** Счета, возвраты и подписки в ответах — UTC `+00:00`, но `GET /tariff` и `GET /account/health` отдают `+05:00` (Asia/Almaty). Частый баг — не учесть эту разницу в дашборде.

## Частые вопросы

**У нас нет своего API — CRM только «умеет ходить наружу». Хватит?**
Да: наружу нужен один POST (создать счёт), внутрь — один URL для вебхука. Если CRM не может принять вебхук, остаётся поллинг `GET /invoices/{id}` — или сборка связки без своего сервера через [n8n](/n8n-integration); но вебхук надёжнее и «мгновеннее».

---

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