ДокументацияВведение
Коды ошибок

Интеграция

Идемпотентность

Безопасные повторы POST-запросов

Идемпотентность гарантирует, что повторный запрос не создаст второй платёж или возврат. Передавайте заголовок Idempotency-Key (обычно UUID v4) на мутирующих POST-запросах — если сеть оборвалась и вы отправили запрос заново с тем же ключом, PayBot вернёт результат первой операции, а не проведёт её дважды.

Ключ обязателен на POST /v2/qr, POST /v2/invoices, POST /v2/invoices/{id}/cancel, POST /v2/refunds и на всех мутирующих /v1/*. Для POST /v2/payment-links он необязателен — бэкенд подставит собственный, но свой ключ защитит от двойного клика.

Как передать ключ идемпотентности

Добавьте заголовок Idempotency-Key со случайной строкой. На практике — UUID версии 4.

bash
Скачать
curl -X POST https://api.paybot.kz/v2/qr \
  -H "X-API-Key: kp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Idempotency-Key: 3f9a1c8e-2b7d-4056-a1c2-e3f4b5d6c7e8" \
  -H "Content-Type: application/json" \
  -d '{"amount": 5000, "comment": "Заказ №1024"}'
python
Скачать
import uuid
import requests

def create_qr(api_key: str, amount: int, comment: str) -> dict:
    idempotency_key = str(uuid.uuid4())  # один ключ на намерение пользователя
    response = requests.post(
        "https://api.paybot.kz/v2/qr",
        headers={
            "X-API-Key": api_key,
            "Idempotency-Key": idempotency_key,
        },
        json={"amount": amount, "comment": comment},
    )
    return response.json()

Как правильно формировать ключ

Генерируйте один ключ на одно намерение пользователя — на нажатие кнопки «Оплатить», а не на каждую HTTP-попытку. Смысл в этом: если запрос упал по таймауту и вы повторяете ту же операцию, повтор должен идти с тем же ключом, иначе PayBot сочтёт его новой операцией и создаст второй платёж.

  • UUID v4 — надёжный дефолт: str(uuid.uuid4()) в Python, crypto.randomUUID() в JS.
  • Ключ можно и осмысленный — например, детерминированный из ID заказа, чтобы повтор оплаты того же заказа гарантированно совпал.
  • Не переиспользуйте один ключ для разных по смыслу операций — совпадение ключей означает «это тот же запрос».

Что происходит при повторе

  • Повтор с тем же ключом на /v1/* вернёт тот же ответ и добавит заголовок X-Idempotent-Replay: true — можно честно показать пользователю «повтор, деньги не списаны».
  • Параллельный запрос с тем же ключом, пока первый ещё выполняется, вернёт 409 idempotency_conflict — не ретрайте агрессивно, дождитесь ответа первого.
  • Отсутствие обязательного ключа даст 400 idempotency_key_required (param: Idempotency-Key) — это баг фронтенда, а не ошибка пользователя, не показывайте её в интерфейсе.

Отдельная защита есть у оплаты подписки: POST /v1/subscription/pay держит Redis-лок на пару (клиент, тариф) 30 секунд, поэтому двойной клик вернёт 429 payment_in_progress — показывайте это как нейтральный тост «подождите 30 секунд», а не как ошибку.

Окно хранения и режим test

Ключ действует в пределах окна дедупликации на бэке: в течение него повтор возвращает сохранённый результат первой операции. По истечении окна тот же ключ снова считается новой операцией — не рассчитывайте, что старый ключ защитит спустя сутки. Для POST /v2/payment-links, где ключ необязателен, бэкенд сам генерирует значение вида pl_auto_.

В тестовом режиме (ключ kp_test_...) идемпотентность работает так же — отлаживайте повторы на тестовых платежах, прежде чем включать боевой ключ.

FAQ

Что будет, если не передать Idempotency-Key?

На обязательных endpoint'ах (/v2/qr, /v2/invoices, /v2/refunds, cancel, все /v1/*) вернётся 400 idempotency_key_required. Исключение — план enterprise на /v1/*, где ключ генерируется автоматически. Для POST /v2/payment-links ключ необязателен и подставляется сам.

Какой ключ использовать — случайный или из ID заказа?

Оба варианта рабочие. Случайный UUID проще и безопасен, если вы держите его на время намерения пользователя. Детерминированный ключ из ID заказа удобнее, когда нужно, чтобы любая повторная попытка оплатить конкретный заказ гарантированно схлопнулась в одну операцию.

Идемпотентность защищает от двойного клика?

Да, если оба клика уходят с одним ключом. Сгенерируйте ключ в момент открытия формы (одно намерение) и шлите его на всех повторах — второй клик тогда либо вернёт результат первого, либо получит 409 idempotency_conflict.

Чем отличается replay от conflict?

X-Idempotent-Replay: true — первая операция уже завершилась, вам вернули её готовый результат. 409 idempotency_conflict — первая операция ещё выполняется прямо сейчас, повтор пришёл слишком рано; подождите и не дублируйте запрос.

Смежные разделы

  • QR и счета — где ключ идемпотентности обязателен.
  • Коды ошибок — как обрабатывать idempotency_key_required и idempotency_conflict.