Best Practices
Дедуп webhook-ов, server-side подтверждение, обработка 429, мониторинг и security checklist.
Дедуплицируйте webhook'и
Webhook может прийти больше одного раза (мы не отвечаем за то, что ваш сервер на ретрае ответил > 15 секунд, и мы решили перепослать). Поле id события стабильно между ретраями. Храните набор обработанных id хотя бы за последние сутки и игнорируйте дубли.
// Postgres-подход: уникальный ключ на event.id, ON CONFLICT DO NOTHING. INSERT INTO balancedpay_events (event_id, kind, payload, created_at) VALUES ($1, $2, $3, NOW()) ON CONFLICT (event_id) DO NOTHING; // Если SQL вернул 0 строк — это дубль, не делаем побочные эффекты.
Подтверждайте оплату сервер-сайдом, не клиент-сайдом
После оплаты мы вернём покупателя на return_url с ?payment_id=…&status=succeeded. Эти параметры может подменить любой пользователь руками. Перед тем как «поздравить с покупкой», подтвердите статус webhook'ом или явным GET /payments/{id} с проверкой status === 'succeeded'.
Idempotency-Key — по одному на бизнес-операцию
Не генерируйте новый ключ на каждый ретрай. Иначе ретрай создаст второй платёж. Привязывайте ключ к бизнес-сущности: например, order:{id}:create для создания платежа, order:{id}:refund:50000 для возврата конкретной суммы. Один и тот же ключ + то же тело → 200 OK с уже созданным объектом.
Не храните карточные данные
Мы не отдаём номер карты, CVV или срок действия — только маски (customer_card_mask: 411111******1111) и бренд. Это PCI DSS-требование. Не пытайтесь распарсить полный PAN из ответа — его там нет и не будет.
Обрабатывайте 429 — это норма
Лимит мерчанта на public-API настраивается в админке balancedpay. При превышении возвращаем 429 rate_limited и Retry-After в секундах. Не игнорируйте этот заголовок: ретрай раньше ничего не даст. Все наши SDK кидают BalancedpayError с code: 'rate_limited' — ловите его и спите.
Test-режим — это test, а не production-debug
В test-режиме платежи проходят через симулятор. Не считайте, что «у меня всё работает на test, в live тоже работает» — банк-эквайер на live добавляет реальные кейсы: 3DS-фейлы, недостаточно средств, заблокированная карта, fraud-блок. Прогоните test-кейсы из раздела «Тестирование» — там сценарии, которые в live действительно случаются.
Мониторьте ключевые метрики
- Доля
payment.failedпоfailure_code. Скачокcard_declined— повод переключиться на резервный коннектор. - Время от
payment.createdдоpayment.succeeded. P95 > 30 сек — что-то с банком. - Доля
429 rate_limitedв ответах. Если >0% — пора договориться с админами balancedpay о повышении лимита. - Возраст последнего успешного webhook'а. Если за последний час ничего не пришло, а оплаты идут — проверьте webhook-endpoint и retry-логику в ЛК.
Security checklist
- API-ключи — в секретах CI/CD или secret manager. Никогда в git и никогда в front-end бандле.
- Webhook-endpoint — только HTTPS на production. HTTP-endpoint мы разрешим (для локалки), но ваши данные пойдут открытым текстом.
- Включите HMAC-подпись запроса (флаг «Требовать подпись» на магазине) — второй фактор поверх Bearer.
- Allow-list IP — обязателен для боевых ключей. Список IP вашего бэкенда, ничего лишнего.
- Ротируйте webhook-секрет раз в полгода (в ЛК — кнопка «Ротация»). Старый перестаёт работать сразу, новый показываем один раз.
- Логируйте
X-Freefin-Event-Idна каждый принятый webhook — это даёт вам корреляцию с нашим логом доставок.