Core Concepts
Idempotency
Retry any mutating request safely by attaching an Idempotency-Key header. LuniPay returns the cached response instead of processing the request a second time.
Why it matters
Network failures happen. A POST that creates a checkout session might time out, or your process might crash after LuniPay wrote the row but before your code saw the response. Without idempotency you have no safe way to retry — you either risk a duplicate charge or you skip the retry and lose the order.
An idempotency key lets you retry with confidence. LuniPay detects the duplicate and returns the response from the original request, unchanged.
How to send a key
Attach an Idempotency-Key header to any mutating request (POST, PATCH, DELETE). The value is yours to choose — a UUID v4 or v7 is the standard pick.
curl https://lunipay.io/api/v1/customers \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Idempotency-Key: 17e8a4cd-3c52-4e15-9d29-5a7cfe6e1eaa" \
-H "Content-Type: application/json" \
-d '{ "email": "kim@example.com", "first_name": "Kim", "last_name": "Chee" }'Replayed responses
The first successful request does the work and caches the response. On any subsequent request with the same key (within 24 hours), LuniPay returns the original response body and status code, and adds a Idempotent-Replayed: true header so you can tell that you got a cached result rather than a fresh one.
Body mismatch
If you retry with the same key but a different request body, LuniPay refuses the request with 409 idempotency_error. That protects you from accidentally overwriting a prior request with different data. The rule is: same key means same request, always.
{
"error": {
"type": "invalid_request_error",
"code": "idempotency_error",
"message": "Idempotency key already used with a different request body."
}
}Scope and expiry
- Scope: keys are scoped to the API key that issued them. Two different API keys using the same key value do not collide.
- Expiry: keys are retained for 24 hours. After that the record is purged and the key can be reused for a brand-new request.
- Live vs test: keys are also scoped to livemode. A test request never matches a live one.
What about errors?
Errors below 500 are cached — retrying a bad request with the same key returns the same error. Errors 500 and above are not cached; they indicate a LuniPay-side failure and are safe to retry with the same key, which will re-process the request.
Generating keys
Use a fresh random UUID for each logical request and stash it in your database before you send. If the request times out, your next retry pulls the stored key and tries again — you never have to wonder whether the previous attempt landed.
import uuid
import lunipay
lunipay.api_key = "sk_test_YOUR_KEY"
key = str(uuid.uuid4())
# Persist `key` alongside the pending order so you can retry.
session = lunipay.CheckoutSession.create(
amount=5000,
currency="usd",
success_url="https://example.com/thanks",
idempotency_key=key,
)A rule of thumb