Skip to main content

Idempotency

The Flowlix API supports idempotency for safely retrying requests without performing the same operation twice. This is critical for payment processing, where network issues or timeouts can leave you unsure whether a request succeeded.

How it works

Include an Idempotency-Key header with a unique value on any POST request:
curl -X POST https://api.flowlix.eu/v1/payments \
  -H "Authorization: Bearer fl_test_sk_abc123def456" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -d '{"amount": 4999, "currency": "eur", "card": {...}}'

Behavior

  1. First request — Flowlix processes the request normally and stores the response against the idempotency key.
  2. Subsequent requests with the same key and same parameters — Flowlix returns the stored response without reprocessing. The response is byte-for-byte identical.
  3. Same key but different parameters — Flowlix returns a 409 Conflict error. You cannot reuse a key with different request bodies.

Key requirements

RuleDetail
FormatAny string up to 255 characters. We recommend UUIDv4.
ScopeKeys are scoped to your API key. Different API keys can use the same idempotency key without conflict.
ExpirationKeys expire after 24 hours. After expiration, the same key can be reused as if it were new.
Applicable methodsOnly POST requests. GET requests are naturally idempotent.

Generating keys

We recommend using UUIDv4 for idempotency keys:
import uuid
idempotency_key = str(uuid.uuid4())
# e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
const idempotencyKey = crypto.randomUUID();
// e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
String idempotencyKey = UUID.randomUUID().toString();
// e.g., "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
For order-based systems, you can also use a deterministic key tied to the operation:
Idempotency-Key: create-payment-order-1234
This ensures that even if your system crashes and restarts, retrying the same order will not create a duplicate payment.

Conflict errors

If you reuse an idempotency key with different request parameters, you get a 409 Conflict:
{
  "error": {
    "type": "idempotency_error",
    "code": "idempotency_key_in_use",
    "message": "Keys for idempotent requests can only be used with the same parameters they were first used with.",
    "param": null,
    "decline_code": null,
    "doc_url": "https://docs.flowlix.eu/api-reference/idempotency",
    "request_id": "req_abc123def456"
  }
}

Best practices

  • Always use idempotency keys for payment creation and refund requests.
  • Generate a new key for each distinct operation — do not reuse keys across different payments.
  • Store the key alongside the order in your database so you can retry with the same key if needed.
  • Use deterministic keys for retries — if retrying a failed request, use the same key as the original attempt.
  • Do not generate keys randomly on each retry — that defeats the purpose. The key must be the same across all attempts of the same operation.

Retry strategy

A recommended retry strategy for payment requests:
  1. Generate an idempotency key for the operation.
  2. Send the request.
  3. If you get a network timeout or 5xx error, wait and retry with the same idempotency key.
  4. Use exponential backoff: wait 1s, 2s, 4s, 8s, up to a maximum of 30s.
  5. After 3-5 retries, stop and alert your operations team.
import time
import requests
import uuid

def create_payment_with_retry(payload, api_key, max_retries=3):
    idempotency_key = str(uuid.uuid4())

    for attempt in range(max_retries + 1):
        try:
            response = requests.post(
                "https://api.flowlix.eu/v1/payments",
                headers={
                    "Authorization": f"Bearer {api_key}",
                    "Content-Type": "application/json",
                    "Idempotency-Key": idempotency_key,
                },
                json=payload,
                timeout=30,
            )

            if response.status_code < 500:
                return response.json()

        except requests.exceptions.RequestException:
            pass  # Network error -- will retry

        if attempt < max_retries:
            wait = min(2 ** attempt, 30)
            time.sleep(wait)

    raise Exception("Payment request failed after retries")