Зачем нужно
Сеть ненадёжна. Ваш POST может «потеряться» — но Pay Bot уже обработал его и не успел вернуть 200. Повтор без идемпотентности создаст второй QR / счёт / возврат. Это особенно опасно для возвратов: можно вернуть клиенту вдвое больше денег.
С Idempotency-Key:
- •При первом запросе Pay Bot обрабатывает и кеширует ответ
- •При повторе с тем же ключом возвращает кешированный ответ за миллисекунды
- •Гарантия: ровно один платёж независимо от количества попыток
Где обязателен
Обязателен на этих эндпоинтах:
- •
POST /v2/qr - •
POST /v2/invoices - •
POST /v2/refunds
Без заголовка Idempotency-Key придёт 400 idempotency_key_required.
Опционален (но рекомендуется) на POST /v2/payment-links — Pay Bot сам генерирует ключ из тела запроса, но явный Idempotency-Key надёжнее.
Не нужен на GET, DELETE, и PATCH-эндпоинтах — они и так идемпотентны.
Как использовать
curl -X POST https://payapi.aibot.kz/v2/qr \
-H "X-API-Key: kp_live_xxx" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"amount": 5000}'import uuid, requests
key = str(uuid.uuid4()) # сгенерировать один раз
for attempt in range(3):
try:
resp = requests.post(
"https://payapi.aibot.kz/v2/qr",
headers={
"X-API-Key": "kp_live_xxx",
"Idempotency-Key": key, # тот же ключ на всех retry
},
json={"amount": 5000},
timeout=10,
)
return resp.json()
except requests.Timeout:
continueconst key = crypto.randomUUID();
async function createWithRetry() {
for (let i = 0; i < 3; i++) {
try {
const res = await fetch("https://payapi.aibot.kz/v2/qr", {
method: "POST",
headers: {
"X-API-Key": "kp_live_xxx",
"Idempotency-Key": key, // тот же ключ
"Content-Type": "application/json",
},
body: JSON.stringify({ amount: 5000 }),
});
return await res.json();
} catch (e) {
if (i === 2) throw e;
}
}
}$key = bin2hex(random_bytes(16));
$ch = curl_init("https://payapi.aibot.kz/v2/qr");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: kp_live_xxx",
"Idempotency-Key: $key", // тот же на retry
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode(["amount" => 5000]),
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);Правила
- •Длина ключа: 1–255 символов
- •Допустимые символы:
[A-Za-z0-9_-] - •TTL кеша: 24 часа
- •Уникальность: в пределах одного аккаунта
- •Тело запроса при повторе должно совпадать — иначе
409 idempotency_conflict
Что в ответе
При повторе Pay Bot добавляет заголовок:
X-Idempotent-Replay: trueТело и статус-код идентичны оригинальному ответу. Логируйте этот заголовок — он помогает понять, что произошёл retry.
Conflict при разных телах
Если вы переиспользовали ключ с другим телом запроса, Pay Bot вернёт:
{
"error": {
"type": "validation_error",
"code": "idempotency_conflict",
"message": "Idempotency-Key уже использован с другим телом запроса",
"request_id": "req_..."
}
}Используйте новый ключ или повторите с оригинальным телом.
Лучшие практики
- •Генерируйте ключ один раз перед циклом retry, не на каждой попытке
- •Сохраняйте ключ в БД вместе с заказом — это даёт идемпотентность даже при перезапуске сервера
- •Используйте бизнес-идентификаторы:
Idempotency-Key: refund_{order_id}_{attempt}илиqr_{cart_id} - •Не реюзайте через 24 часа — кеш истёк, повтор создаст новую операцию
Пример: устойчивое создание возврата
import uuid, requests
def create_refund(order_id, amount):
# Ключ зависит от order_id — гарантия одного возврата на заказ
idem = f"refund_{order_id}"
resp = requests.post(
"https://payapi.aibot.kz/v2/refunds",
headers={
"X-API-Key": "kp_live_xxx",
"Idempotency-Key": idem,
},
json={"operation_id": find_op_id(order_id), "amount": amount},
timeout=10,
)
if resp.headers.get("X-Idempotent-Replay") == "true":
logger.info(f"refund replay for order {order_id}")
return resp.json()Даже если функция вызывается N раз для одного заказа — возврат произойдёт один раз.
Что дальше
- •Возвраты — обязательно используйте идемпотентность
- •QR и счета — обязательно идемпотентны
- •Коды ошибок —
idempotency_conflict,idempotency_key_required