> Источник: https://apipay.kz/guides/apipay-dlya-svoey-crm · Обновлено: 2026-07-06 · 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` ≤500, `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. **Песочница**: прогоните весь цикл на sandbox-организации — там есть simulate-оплата. Внимание: при переходе в рабочий режим тестовые организации и их ключи удаляются — ключи прода будут новые.
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` — не зависание.** При высокой нагрузке счёт легитимно может висеть в `processing` дольше 60 минут; система сама ретраит. Не пересоздавайте — смотрите `last_kaspi_error_*` в `GET /invoices/{id}`.
4. **Каталог и скидки.** Если у вас Kaspi ОФД, без `cart_items` будет 422; при своих скидках надёжнее пересчитать цену на своей стороне и передать `price` позиции явно — см. [cart_items и 422](/guides/scheta-s-korzinoy-cart-items-ofd).
5. **Ключи после прода.** После перехода из песочницы ключи другие; «вчера работало, сегодня 401» — почти всегда старый ключ в конфиге CRM.

## Типовой сценарий: карточка двигается сама

Обобщённый пример. У компании самописная CRM без собственного платёжного API, а вход в Kaspi возможен только по номеру телефона. Задача сводится к простому: выставлять счёт покупателю-физлицу и автоматически двигать карточку сделки, когда оплата прошла. Интеграция строится так: кнопка «Выставить счёт Kaspi» в карточке вызывает `POST /invoices`, где ключом идемпотентности служит `deal_id` сделки; вебхук-эндпоинт проверяет подпись и ставит фоновую задачу передвинуть сделку в нужный статус. Когда со временем счета начинает выставлять и вторая система (например, учётная), ей заводят отдельный токен — счета обеих систем различают по полю `source` в вебхуке. Ручная сверка по утрам отпадает: `GET /invoices/{id}` дёргается только если карточка «застряла».

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

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

`POST /subscriptions`: `phone_number`, `billing_period` (`daily`/`weekly`/`biweekly`/`monthly`/`quarterly`/`yearly`), `billing_day` (1–28), сумма — `amount` (min 100) либо `cart_items` (для каталог-организации), `external_subscriber_id` = **ID клиента в CRM**, параметры ретраев (`max_retry_attempts` 1–10, `retry_interval_hours` 1–168, `grace_period_days` 1–30), `bill_immediately` (выставить первый счёт сразу).

```bash
curl -X POST https://api.apipay.kz/api/v1/subscriptions \
  -H "X-API-Key: ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
  -d '{ "phone_number": "8XXXXXXXXXX", "amount": 15000,
        "billing_period": "monthly", "billing_day": 5,
        "subscriber_name": "Клиент CRM #88",
        "external_subscriber_id": "crm-client-88",
        "description": "Абонемент, ежемесячно" }'
```

Что система делает **сама** (CRM ничего не пересоздаёт): в срок выставляет счёт → по нему идут обычные invoice-вебхуки; при неоплате шлёт `subscription.payment_failed` и перевыставляет счёт, пока не исчерпаны попытки; затем `subscription.grace_period_started`; после grace — `subscription.expired`, и биллинг останавливается **навсегда** (реактивации нет — заводите новую подписку). Управление: `pause`/`resume`/`cancel` (`resume` пересчитывает `next_billing_at` от текущего момента, пропущенные периоды не доначисляются).

Важно: два нюанса для CRM. События `subscription.*` **не пишутся в webhook-логи** и не имеют ручного retry — дедупьте по `(событие, subscription.id)` и не теряйте. И **известный gap**: счёт подписки со `status=error` (например `client_not_found`) провалом платежа **не считается** — `payment_failed` не приходит, период «пропущен»; CRM узнаёт об этом только из invoice-вебхука `error`. Полный жизненный цикл, кейсы и цены — на витрине [Рекуррентные платежи Kaspi](/recurring-payments-kaspi) и в статье [Подписки ApiPay](/guides/podpiski-apipay).

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

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

Управлять кассирами прямо из CRM можно через `/connections*` — но только если у ключа включён флаг `can_manage_cashiers` (включает **владелец** в кабинете; без флага — `403 cashier_management_disabled`). Переавторизация «касса слетела» — три шага: `auth/init` → `auth/send-phone` (Kaspi шлёт SMS кассиру на `7XXXXXXXXXX`) → `auth/verify-otp`. Разделение отчётности и счетов по точкам — [Раздельная отчётность по точкам](/guides/razdelnaya-otchetnost-po-tochkam).

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

Фоновый мониторинг CRM строится на `GET /account/health`: поле `connection.session_status` — так 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); но вебхук надёжнее и «мгновеннее».

**Что писать в `external_order_id` и чем он отличается от `..._idempotency`?**
`external_order_id` — просто ваша метка, вернётся в вебхуке для поиска сделки. `external_order_id_idempotency` — защитный ключ от дублей (повтор → 409). Обычно оба равны ID сделки.

**Можно ли с двух CRM-аккаунтов выставлять счета от одного Kaspi?**
Да — заведите каждому отдельный API-токен, так удобнее различать источники; тариф от числа токенов не меняется.

**Как протестировать без реальных оплат?**
В песочнице: тот же API, simulate-оплата, `webhook.test` для проверки подписи. Подписки прогоняются симуляциями `simulate-invoice`/`start-simulation`/`stop-simulation`. Перед продом перечитайте [песочница и рабочий режим](/guides/pesochnitsa-i-rabochiy-rezhim) — ключи в проде будут новые.

**Можно ли делать регулярные списания/абонементы из CRM?**
Не автосписание с карты (в Kaspi его нет), а авто-**выставление** счетов: подписка сама создаёт счёт в срок, клиент подтверждает push'ем. Заводится через `POST /subscriptions`, привязка к клиенту CRM — `external_subscriber_id`. Истёкшую подписку не реактивировать — создавать новую. Подробно — [Подписки ApiPay](/guides/podpiski-apipay).

**Как выставлять счета с разных касс или филиалов одной организации?**
Передавайте `kaspi_connection_id` при создании счёта. Если активных касс больше одной и основная не назначена — без параметра вернётся `422 connection_ambiguous`. Разделение отчётности по точкам — в статье [Раздельная отчётность по точкам](/guides/razdelnaya-otchetnost-po-tochkam).

---

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