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

# Чек-лист безопасности интеграции ApiPay

**TL;DR.** Безопасная интеграция держится на четырёх вещах: API-ключ живёт **только на сервере** (никогда в браузере, боте-клиенте или репозитории), входящие вебхуки проверяются **подписью по сырому телу запроса** (`X-Webhook-Signature: sha256=<hex>`), одинаковые запросы защищены **идемпотентностью** (`external_order_id_idempotency` → 409 на дубль), а секреты вы **сохраняете один раз при создании** и ротируете при утечке. Ниже — 12 пунктов: у каждого «зачем» и «как проверить у себя». Смена API-ключа и смена секрета подписи — **две разные операции**: перегенерация ключа вебхук-секрет не трогает.

## Коротко

| Пункт | Суть |
|---|---|
| Ключ на сервере | X-API-Key — только backend, никогда во фронтенде/боте/репозитории |
| Подпись вебхука | Проверять `X-Webhook-Signature` по **raw body**, до парсинга JSON |
| Ключ ≠ секрет | X-API-Key аутентифицирует ваши запросы; секрет проверяет чужие вебхуки |
| Идемпотентность | `external_order_id_idempotency` ≤191 симв. → 409 на повтор |
| Утечка ключа | «Сменить ключ» в кабинете; секрет подписи меняется **отдельной** кнопкой |
| Показ один раз | Ключ и секрет видны только при создании — сразу в секрет-менеджер |
| Доступы | Менеджеров ≤5, ревизия раз в квартал |
| Sandbox ≠ prod | Тестовые ключи в проде не работают — по дизайну |

## 1. API-ключ — только на сервере

**Зачем.** `X-API-Key` даёт право создавать счета от имени вашей организации, а при включённом флаге — управлять кассирами. Попав в браузер, публичный репозиторий или клиентскую часть Telegram-бота, ключ становится доступен любому.

**Как проверить.** Прогоните `git grep` по значению ключа во всей истории репозитория; убедитесь, что `.env` в `.gitignore`; откройте DevTools на своём сайте и проверьте, что в сетевых запросах фронтенда нет заголовка `X-API-Key` — запрос в ApiPay должен уходить **с вашего бэкенда**, а не из браузера покупателя.

## 2. Проверяйте подпись вебхука по сырому телу

**Зачем.** Без проверки подписи любой, кто узнал ваш webhook URL, пришлёт фейковое «счёт оплачен». Подпись — единственная гарантия, что уведомление действительно от ApiPay.

**Как проверить.** Подпись считается так: `'sha256=' . hash_hmac('sha256', <сырое тело запроса>, webhook_secret)`. Ключевое слово — **сырое**: считайте HMAC до того, как фреймворк распарсит и пересоберёт JSON (перестановка полей или пробелы изменят байты и сломают сверку). Нажмите в кабинете «Проверить уведомления» на карточке ключа — придёт событие `webhook.test` с боевой подписью; убедитесь, что ваш обработчик её принял.

## 3. Не путайте API-ключ и секрет подписи

**Зачем.** Это две разные строки с разными ролями, и их регулярно путают. **API-ключ** (`X-API-Key`) — в заголовке ваших **исходящих** запросов к ApiPay. **Секрет подписи вебхука** (webhook secret) — для проверки **входящих** уведомлений (`X-Webhook-Signature`). Ключ наружу не показывают; секрет наружу тоже не показывают — но перепутать их местами означает, что ни аутентификация, ни проверка подписи не сработают. Подробный разбор — в статье «[API-ключ и секрет подписи вебхука](/guides/api-klyuch-i-webhook-secret)».

## 4. Защититесь от дублей идемпотентностью

**Зачем.** Сетевой сбой или ретрай на вашей стороне не должен превращаться во второй счёт покупателю. Передавайте `external_order_id_idempotency` (до 191 символа, уникален в пределах организации) — повтор с тем же ключом вернёт **409** с `invoice_id` и статусом ранее созданного счёта, а не создаст новый.

**Как проверить.** Отправьте один и тот же запрос дважды подряд: первый — 201, второй — 409 с прежним `invoice_id`. Гонка двух параллельных запросов тоже безопасна — её разрешает уникальный индекс в базе.

## 5. При утечке ключа — «Сменить ключ» (секрет при этом НЕ меняется)

**Зачем.** Если ключ мог утечь — не ждите. В кабинете: **Настройки → Подключение → карточка ключа → меню «Ещё» → «Сменить ключ»**. Старый ключ мгновенно перестаёт работать. **Важно:** смена ключа **не трогает** секрет подписи вебхука — это отдельная запись. Если у вас есть основания думать, что утёк и секрет, смените его отдельно: в том же меню «Ещё» — **«Сменить секретный ключ подписи»** (кнопка появляется только когда у ключа задан адрес уведомлений).

**Как проверить.** После смены ключа убедитесь, что старый возвращает 401, а новый работает; после смены секрета — что ваш обработчик пересчитывает подпись новым значением.

## 6. Ключ и секрет показываются только один раз

**Зачем.** На финальном экране создания ключа кабинет прямо предупреждает: «API-ключ и секретный ключ показываются только один раз». Позже в карточке виден лишь хвост — `****key_hint`. Восстановить полное значение нельзя, только перегенерировать.

**Как проверить.** Сразу при создании нажмите «Скопировать всё для ИИ-помощника» или сохраните ключ и секрет в секрет-менеджер (Vault, GitHub Actions Secrets, переменные окружения хостинга) — не в заметки и не в код.

## 7. Ревизуйте доступы к кабинету

**Зачем.** В организацию можно пригласить до **5 менеджеров**. Уволенный сотрудник с доступом — это открытая дверь.

**Как проверить.** Раз в квартал открывайте **Настройки → Менеджеры** и убирайте тех, кто больше не должен иметь доступ.

## 8. Sandbox-ключи не работают в проде

**Зачем.** Ключ жёстко привязан к организации. Тестовая организация «архитектурно» не может стать боевой, а при переходе в рабочий режим тестовые организации и их ключи удаляются. Значит песочный ключ в продакшене просто не аутентифицируется.

**Как проверить.** В прод-конфиге должен лежать ключ **боевой** организации, а не тот, что вы получили в песочнице. Держите их в разных переменных окружения. Подробнее — «[Песочница и рабочий режим](/guides/pesochnitsa-i-rabochiy-rezhim)».

## 9. Не логируйте полные ключи

**Зачем.** Логи утекают чаще, чем базы. Полный ключ в логе приложения = утечка.

**Как проверить.** В своих логах маскируйте секреты до последних 4 символов (`****key_hint` — ровно то, что показывает кабинет). Настройте фильтр логгера, который вырезает заголовки `X-API-Key` и `X-Webhook-Signature`.

## 10. Доверяйте подписи, а не IP

**Зачем.** Webhook URL должен быть **HTTPS** — приватные и внутренние адреса ApiPay отклоняет с 422 (защита от SSRF). Но подлинность отправителя гарантирует **подпись**, а не IP-адрес.

**Как проверить.** Не фильтруйте вебхуки по «белому списку IP» — фиксированный список адресов мы не публикуем. Единственная надёжная проверка — HMAC-подпись из пункта 2.

## 11. Ограничьте право ключа управлять кассирами

**Зачем.** У API-ключа есть флаг «управление кассирами». По умолчанию он **выключен**, и включить его может **только владелец**. Иначе утёкший ключ смог бы отключить кассира и остановить приём платежей.

**Как проверить.** Оставляйте флаг выключенным для всех ключей, которым это не нужно (обычные интеграции создания счетов в нём не нуждаются).

## 12. Ротируйте по расписанию и при смене команды

**Зачем.** Даже неутёкший ключ стоит менять периодически и обязательно — при уходе разработчика, у которого он мог остаться.

**Как проверить.** Заведите регламент: плановая ротация ключей и секретов, внеплановая — при любом кадровом или инфраструктурном изменении. Механика — пункт 5.

## Частые ошибки

- **Секрет подписи не проверяют вообще** — обработчик принимает любой POST. Первым делом включите проверку HMAC (пункт 2).
- **Считают подпись по пересобранному JSON**, а не по raw body — сверка всегда падает.
- **Меняют ключ и ждут, что сменится секрет** — это разные кнопки (пункт 5).
- **Хардкодят sandbox-ключ**, потом удивляются 401 в проде (пункт 8).
- **Отдают ключ фронтенду ради «простоты»** — и раздают всем посетителям сайта.

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

**Смена API-ключа меняет секрет подписи вебхука?**
Нет. Это две разные операции над записью ключа: «Сменить ключ» меняет только сам ключ, «Сменить секретный ключ подписи» — только секрет. Вебхук продолжает работать со старым секретом, пока вы не смените его отдельно.

**Как проверить подпись вебхука?**
Посчитайте `hash_hmac('sha256', сырое_тело_запроса, webhook_secret)`, добавьте префикс `sha256=` и сравните с заголовком `X-Webhook-Signature`. Сравнивайте по сырому телу до парсинга JSON.

**Что делать, если API-ключ утёк?**
В кабинете: карточка ключа → «Ещё» → «Сменить ключ». Старый умрёт сразу. Если мог утечь и секрет подписи — смените его отдельной кнопкой.

**Можно ли восстановить потерянный ключ или секрет?**
Нет, они показываются один раз при создании. Потеряли — перегенерируйте (это сделает старое значение недействительным).

**Можно ли ограничить вебхуки по IP отправителя?**
Не рекомендуем: фиксированный список IP мы не публикуем. Полагайтесь на проверку подписи — это надёжнее.

**Видит ли ApiPay мои деньги и берёт ли процент с оплат?**
Нет. Деньги идут напрямую на ваш Kaspi-счёт, ApiPay доступа к ним не имеет; тариф — фиксированная подписка без процента с оборота. Kaspi удерживает свою обычную комиссию за приём платежа (~0,95%) — она не связана с ApiPay и действует при любом способе приёма через Kaspi Pay.

---

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