Learn how to use idempotency keys to safely retry API requests without duplicating operations.
What is Idempotency?
Idempotency is the property of an API operation where making the same request multiple times produces the same result as making it once. This is critical for payment processing and order management, where duplicate operations can result in double charges or duplicate orders.
DEUNA APIs support idempotency, allowing you to safely retry requests without concern for accidentally generating the same operation twice.
Idempotency is entirely optional but strongly recommended for any operation that creates or modifies resources (e.g., payments, orders, refunds).
How It Works
When you send a request with an idempotency key:
- First request β DEUNA processes the request normally and stores the response (status code + payload) associated with your idempotency key.
- Subsequent requests with the same key β DEUNA returns the stored response from the original request without reprocessing it.
- Different key β DEUNA treats it as a new, independent request.
ββββββββββββ ββββββββββββββββ ββββββββββββββββ
β Client ββββββββββΆβ DEUNA API ββββββββββΆβ Provider β
β β β β β β
β Request β β Check key: β β Process β
β + Key β β New? Process β β payment β
β β β Exists? Returnβ β β
β βββββββββββ cached resp βββββββββββ Response β
ββββββββββββ ββββββββββββββββ ββββββββββββββββSupported HTTP Methods
Idempotency keys are supported for the following HTTP methods:
| HTTP Method | Idempotency Support | Notes |
|---|---|---|
POST | β Supported | Required for payment creation, order creation, refunds |
GET | βͺ Not needed | GET requests are naturally idempotent |
PUT | βͺ Not needed | PUT requests are naturally idempotent |
PATCH | βͺ Not needed | PATCH requests are naturally idempotent |
DELETE | βͺ Not needed | DELETE requests are naturally idempotent |
Idempotency keys are primarily used withPOSTrequests, since these are the ones that create new resources.
Implementation Guide
Step 1: Generate an Idempotency Key
Generate a unique key for each distinct operation. We recommend using UUID v4 format.
// JavaScript β Generate a UUID v4 idempotency key
const { v4: uuidv4 } = require('uuid');
const idempotencyKey = uuidv4();
// Example output: "f47ac10b-58cc-4372-a567-0e02b2c3d479"# Python β Generate a UUID v4 idempotency key
import uuid
idempotency_key = str(uuid.uuid4())
# Example output: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
Important: Each unique business operation must have its own idempotency key. Do NOT reuse keys across different operations.
Step 2: Include the Header in Your Request
Add the X-Idempotency-Key header to your API request.
curl -X POST https://api.deuna.io/v1/orders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: f47ac10b-58cc-4372-a567-0e02b2c3d479" \
-d '{
"order": {
"order_id": "order-12345",
"currency": "USD",
"items_total_amount": 5000,
"total_amount": 5000
}
}'// JavaScript β POST request with idempotency key using fetch
const idempotencyKey = uuidv4();
const response = await fetch('https://api.deuna.io/v1/orders', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'X-Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
order: {
order_id: 'order-12345',
currency: 'USD',
items_total_amount: 5000,
total_amount: 5000,
},
}),
});
const data = await response.json();
console.log(data);# Python β POST request with idempotency key using requests
import requests
import uuid
idempotency_key = str(uuid.uuid4())
response = requests.post(
'https://api.deuna.io/v1/orders',
headers={
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'X-Idempotency-Key': idempotency_key,
},
json={
'order': {
'order_id': 'order-12345',
'currency': 'USD',
'items_total_amount': 5000,
'total_amount': 5000,
}
}
)
print(response.json())Step 3: Implement Retry Logic
When a request fails due to network issues or timeouts, retry with the same idempotency key to avoid duplicate operations.
// JavaScript β Retry logic with idempotency
async function makeIdempotentRequest(url, payload, maxRetries = 3) {
const idempotencyKey = uuidv4(); // Generate ONCE per operation
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'X-Idempotency-Key': idempotencyKey, // Same key on every retry
},
body: JSON.stringify(payload),
});
if (response.ok) {
return await response.json();
}
// Don't retry on client errors (4xx) except 408, 429
if (response.status >= 400 && response.status < 500
&& response.status !== 408 && response.status !== 429) {
throw new Error(`Client error: ${response.status}`);
}
console.log(`Attempt ${attempt} failed (${response.status}). Retrying...`);
} catch (error) {
if (attempt === maxRetries) throw error;
console.log(`Attempt ${attempt} failed: ${error.message}. Retrying...`);
}
// Exponential backoff: 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt - 1) * 1000));
}
}# Python β Retry logic with idempotency
import time
import uuid
import requests
def make_idempotent_request(url, payload, max_retries=3):
idempotency_key = str(uuid.uuid4()) # Generate ONCE per operation
for attempt in range(1, max_retries + 1):
try:
response = requests.post(
url,
headers={
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
'X-Idempotency-Key': idempotency_key, # Same key on every retry
},
json=payload,
timeout=30,
)
if response.ok:
return response.json()
# Don't retry on client errors (4xx) except 408, 429
if 400 <= response.status_code < 500 \
and response.status_code not in (408, 429):
raise Exception(f"Client error: {response.status_code}")
print(f"Attempt {attempt} failed ({response.status_code}). Retrying...")
except requests.exceptions.RequestException as e:
if attempt == max_retries:
raise
print(f"Attempt {attempt} failed: {e}. Retrying...")
# Exponential backoff: 1s, 2s, 4s
time.sleep(2 ** (attempt - 1))Best Practices
β
Do's
- Generate a unique key per operation β Use UUID v4 or a similar globally unique identifier.
- Reuse the same key for retries β When retrying a failed request, always use the same idempotency key.
- Store the key alongside the operation β Persist the key in your database so you can retry if needed.
- Use exponential backoff for retries β Avoid overwhelming the API with rapid retry attempts.
- Set a reasonable timeout β DEUNA's production timeout is 60 seconds.
β Don'ts
- Don't reuse keys for different operations β Each new business operation (new payment, new order) must use a fresh key.
- Don't use sequential or predictable keys β Sequential keys can lead to collisions. Always use random UUIDs.
- Don't change the request body with the same key β Sending a different payload with the same key will result in an error.
- Don't rely on idempotency as a substitute for proper error handling β Always implement proper error handling alongside idempotency.
Common Use Cases
Payment Processing
# Creating a payment β use idempotency to prevent double charges
curl -X POST https://api.deuna.io/v1/merchants/payments \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: pay-a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-d '{
"token": "order_token_here",
"payment_source": {
"card_token": "card_token_here"
}
}'Order Creation
# Creating an order β use idempotency to prevent duplicate orders
curl -X POST https://api.deuna.io/v1/orders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ord-a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-d '{
"order": {
"order_id": "my-order-001",
"currency": "USD",
"items_total_amount": 10000,
"total_amount": 10000
}
}'Refunds
# Processing a refund β use idempotency to prevent double refunds
curl -X POST https://api.deuna.io/v1/merchants/transactions/{transaction_id}/refund \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: ref-a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-d '{
"reason": "Customer requested refund",
"amount": 5000
}'Error Handling
| Scenario | Behavior | Recommended Action |
|---|---|---|
| First request succeeds | Response stored with key | Store response, proceed normally |
| Retry with same key + same body | Returns cached response | Use the response as if it were fresh |
| Retry with same key + different body | Returns 422 Unprocessable Entity | Generate a new key for the new operation |
| Key has expired | Treated as a new request | Generate a new key and retry |
| Network timeout | No response received | Retry with the same key |
| Server error (5xx) | Operation may or may not have completed | Retry with the same key |
Idempotency Key Lifecycle
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β Created ββββββΆβ Active ββββββΆβ Expired β
β β β β β β
β Key sent in β β Response is β β Key is no β
β first req β β cached and β β longer validβ
β β β returned on β β β
β β β retries β β β
βββββββββββββββ βββββββββββββββ βββββββββββββββ- Created β The key is sent for the first time with a request.
- Active β DEUNA has processed the request and cached the response. Any subsequent request with the same key returns the cached response.
- Expired β After a period of time, the key expires and a new request with the same key would be treated as a new operation.
Tip: Always generate a new idempotency key for each distinct business operation, even if a previous key has expired.
Testing in Sandbox
You can test idempotent behavior in the sandbox environment:
-
Send a request with an idempotency key to the sandbox URL:
https://api.sandbox.deuna.io -
Send the same request again with the same key and verify you receive the exact same response.
-
Send a request with a different key and verify a new resource is created.
-
Change the request body with the same key and verify you receive an error.
Use the DEUNA Postman Collection for quick testing.
Next Steps
- DEUNA API Overview β Learn about the full API architecture.
- Response Codes β Understand API error codes and how to handle them.
- Environments β Learn about Sandbox and Production environments.
- Rate Limits β Understand API rate limiting policies.
- Webhooks β Set up webhooks for asynchronous event notifications.