Оптовая платформа цифровых товаров

Idempotency keys в B2B order API 2026: deep dive с UUIDv4 и dedup-окном

Полный гид по idempotency keys для B2B order API: генерация UUIDv4, 24-часовое dedup-окно, retry-safe POST /v1/orders.

Idempotency keys в B2B order API 2026: deep dive с UUIDv4 и dedup-окном

Idempotency key — это техника, которая делает retry-safe-ом любой POST-запрос в B2B API. Без неё каждый network timeout на стороне клиента превращается в вопрос «заказ создался или нет?» — и в 30% случаев ответ «оба раза». В этой статье разбираем production-паттерны, заимствованные у Stripe, и применённые в FoxReload Orders API.

1. Почему один retry создаёт два заказа

Простая последовательность: ваш ERP делает POST /v1/orders с payload {sku: "PSN_TR_50", qty: 1}. FoxReload создаёт заказ, начинает delivery, но TCP-ответ теряется (network blip). Ваш HTTP-клиент через 30s делает retry — FoxReload видит новый запрос, создаёт второй заказ, списывает второй раз с баланса. Результат: дубль.

С idempotency key такой сценарий невозможен — сервер видит тот же ключ, возвращает оригинальный response, второй заказ не создаётся.

2. Генерация UUIDv4 на клиенте

Ключ обязан быть глобально уникальным и непредсказуемым. UUIDv4 — стандартный выбор:

import { randomUUID } from 'crypto';
import axios from 'axios';

async function createOrder(sku: string, qty: number) {
  const idempotencyKey = randomUUID(); // UUIDv4
  return axios.post('https://api.foxreload.com/v1/orders', {
    sku, qty, currency: 'USD',
  }, {
    headers: {
      'Authorization': `Bearer ${process.env.FOXRELOAD_KEY}`,
      'Idempotency-Key': idempotencyKey,
    },
    timeout: 30000,
  });
}

Сохраняйте idempotencyKey в вашей БД до того, как делать запрос. При retry читайте тот же ключ — не генерируйте новый.

3. Server-side dedup window

FoxReload хранит каждый ключ в Redis с TTL 24h. Логика обработки:

// Псевдокод server-side
async function handleCreateOrder(key: string, payload: OrderPayload) {
  const existing = await redis.get(`idem:${key}`);
  if (existing) {
    const stored = JSON.parse(existing);
    if (hash(payload) !== stored.payloadHash) {
      return { status: 422, error: 'idempotency_conflict' };
    }
    return stored.response; // повтор — возвращаем оригинал
  }
  const response = await createOrderInternal(payload);
  await redis.set(`idem:${key}`, JSON.stringify({
    payloadHash: hash(payload),
    response,
  }), 'EX', 86400);
  return response;
}

Race condition между двумя одновременными запросами с одним ключом решается через Redis SETNX + Postgres advisory lock на idempotency_key.

4. Сравнение dedup-storages

Storage Throughput Latency TTL Стоимость
Redis (hot) 200k/s <1ms 24h $0.40/M
Postgres UNIQUE 30k/s 5–8ms $0.10/M
DynamoDB 100k/s 8–12ms 24h $1.25/M
Memcached 500k/s <1ms 24h $0.30/M

FoxReload использует Redis-кластер из 6 нод, sharded по hash(idempotency_key). Это даёт <2ms p99 latency на dedup-проверке.

CTA

Idempotency-Key поддерживается на всех POST/PUT/DELETE-эндпоинтах FoxReload API. Полный референс — после онбординга, запросите доступ.

Часто задаваемые вопросы

Что произойдёт, если я отправлю один Idempotency-Key с разным payload?
FoxReload вернёт HTTP 422 idempotency_conflict с original-response в payload. Это защищает от ситуаций, когда клиент сгенерировал тот же key для другого заказа (баг). На клиенте — alert и manual review.
Какой TTL у idempotency key на стороне FoxReload?
24 часа с момента первого использования. Через 24h тот же ключ можно использовать заново — но не нужно, лучше всегда генерировать новый UUIDv4 на каждый attempt.
Нужен ли Idempotency-Key для GET-запросов?
Нет. GET идемпотентен по определению — повтор GET не создаёт side effects. Idempotency-Key обязателен только для POST /v1/orders, POST /v1/refunds, POST /v1/transfers.
Что если FoxReload вернул timeout — заказ создался или нет?
Повторите запрос с тем же Idempotency-Key. Если original-запрос дошёл — получите тот же order_id и status. Если не дошёл — заказ создастся. В любом случае дублей не будет.
Получить доступ к FoxReload API

Похожие статьи